Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: systopia/drupal-json_forms
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 0.2.0
Choose a base ref
...
head repository: systopia/drupal-json_forms
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Loading
Showing with 2,883 additions and 496 deletions.
  1. +74 −0 README.md
  2. +12 −14 composer.json
  3. +44 −0 js/disable-buttons-on-ajax.js
  4. +124 −0 js/number-input.js
  5. +0 −52 js/submit.js
  6. +49 −0 js/vertical-tabs.js
  7. +3 −1 json_forms.info.yml
  8. +16 −3 json_forms.libraries.yml
  9. +23 −0 json_forms.module
  10. +1 −1 modules/json_forms_example/json_forms_example.info.yml
  11. +2 −2 modules/json_forms_example/src/Form/JsonFormsExampleForm.php
  12. +3 −0 phpcs.xml.dist
  13. +0 −4 phpstan.neon.dist
  14. +39 −9 src/Form/AbstractJsonFormsForm.php
  15. +2 −1 src/Form/ConcreteFormArrayFactoryInterface.php
  16. +64 −17 src/Form/Control/ArrayArrayFactory.php
  17. +45 −8 src/Form/Control/Callbacks/ArrayCallbacks.php
  18. +51 −0 src/Form/Control/Callbacks/DateValueCallback.php
  19. +45 −0 src/Form/Control/Callbacks/EmailValidateCallback.php
  20. +40 −0 src/Form/Control/Callbacks/HtmlCallbacks.php
  21. +76 −0 src/Form/Control/Callbacks/OptionValueCallbacks.php
  22. +4 −94 src/Form/Control/Callbacks/RecalculateCallback.php
  23. +22 −51 src/Form/Control/Callbacks/SelectCallbacks.php
  24. +59 −0 src/Form/Control/Callbacks/StringValueCallback.php
  25. +45 −0 src/Form/Control/Callbacks/UrlValidateCallback.php
  26. +143 −0 src/Form/Control/Callbacks/Util/RecalculateCallbackUtil.php
  27. +46 −0 src/Form/Control/Callbacks/ValueElementValueCallback.php
  28. +12 −2 src/Form/Control/CheckboxArrayFactory.php
  29. +5 −3 src/Form/Control/CheckboxesArrayFactory.php
  30. +4 −1 src/Form/Control/DateArrayFactory.php
  31. +2 −1 src/Form/Control/DatetimeArrayFactory.php
  32. +4 −1 src/Form/Control/EmailArrayFactory.php
  33. +12 −3 src/Form/Control/HiddenArrayFactory.php
  34. +89 −0 src/Form/Control/HtmlArrayFactory.php
  35. +3 −1 src/Form/Control/NumberArrayFactory.php
  36. +5 −32 src/Form/Control/ObjectArrayFactory.php
  37. +23 −11 src/Form/Control/RadiosArrayFactory.php
  38. +65 −19 src/Form/Control/Rule/StatesArrayFactory.php
  39. +3 −1 src/Form/Control/Rule/StatesArrayFactoryInterface.php
  40. +76 −22 src/Form/Control/Rule/StatesBuilder.php
  41. +12 −3 src/Form/Control/SelectArrayFactory.php
  42. +5 −2 src/Form/Control/StringArrayFactory.php
  43. +2 −1 src/Form/Control/SubmitButtonArrayFactory.php
  44. +4 −1 src/Form/Control/UrlArrayFactory.php
  45. +17 −6 src/Form/Control/Util/BasicFormPropertiesFactory.php
  46. +65 −7 src/Form/Control/Util/{OptionsBuilder.php → OptionsUtil.php}
  47. +82 −0 src/Form/Control/ValueArrayFactory.php
  48. +6 −0 src/Form/FormArrayFactory.php
  49. +15 −0 src/Form/Layout/AbstractLayoutArrayFactory.php
  50. +65 −0 src/Form/Layout/TableArrayFactory.php
  51. +37 −6 src/Form/Layout/TableRowArrayFactory.php
  52. +7 −5 src/Form/Markup/HtmlMarkupArrayFactory.php
  53. +50 −0 src/Form/Util/DescriptionDisplayUtil.php
  54. +6 −1 src/Form/Util/FactoryRegistrator.php
  55. +1 −1 src/Form/Util/FieldNameUtil.php
  56. +83 −0 src/Form/Util/FormValidationUtil.php
  57. +16 −11 src/Form/Util/JsonConverter.php
  58. +8 −2 src/Form/Validation/FormValidationMapper.php
  59. +11 −10 src/Form/Validation/FormValidator.php
  60. +11 −10 src/Form/Validation/FormValidatorInterface.php
  61. +11 −10 src/Form/Validation/OpisValidatorFactory.php
  62. +11 −10 src/Form/Validation/ValidationResult.php
  63. +52 −6 src/JsonForms/Definition/Control/ControlDefinition.php
  64. +22 −0 src/JsonForms/Definition/Control/ObjectControlDefinition.php
  65. +91 −0 src/JsonForms/Definition/Custom/CustomDefinition.php
  66. +14 −7 src/JsonForms/Definition/{Markup → Custom}/MarkupDefinition.php
  67. +22 −7 src/JsonForms/Definition/DefinitionFactory.php
  68. +14 −2 src/JsonForms/Definition/DefinitionInterface.php
  69. +38 −0 src/JsonForms/Definition/Layout/ArrayLayoutDefinition.php
  70. +54 −8 src/JsonForms/Definition/Layout/LayoutDefinition.php
  71. +70 −0 src/JsonForms/Definition/Layout/ObjectLayoutDefinition.php
  72. +18 −1 src/JsonForms/ScopePointer.php
  73. +52 −6 tests/src/Unit/Form/Control/CheckboxArrayFactoryTest.php
  74. +4 −4 tests/src/Unit/Form/Control/HiddenArrayFactoryTest.php
  75. +27 −7 tests/src/Unit/Form/Markup/HtmlMarkupArrayFactoryTest.php
  76. +457 −7 tests/src/Unit/Form/Rule/StatesArrayFactoryTest.php
  77. +1 −1 tests/src/Unit/Form/Util/FieldNameUtilTest.php
  78. +46 −0 translations/de.po
  79. +41 −0 translations/json_forms.pot
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# JSON Forms for Drupal

JSON Forms for Drupal is an implementation of the [JSON Forms](
https://jsonforms.io/) specification for Drupal.

## Additional features and keywords

In this implementation there are some custom features and keywords not
specified in standard JSON Forms. (TODO: Not all additional possibilities are
described here, yet.)

### Array control: options `itemLayout` and `elements`

There are two additional options in controls referencing an array.

The option `itemLayout` allows to specify the layout for the individual controls
of an array item (e.g. `VerticalLayout`). The default is `TableRow`.

The option `elements` allows to specify the controls for the properties of an
array item. By default a control is generated for each item property.

Example:
```json
{
"itemLayout": "VerticalLayout",
"elements": [
{
"type": "Control",
"label": "Name",
"scope": "#/properties/name"
},
{
"type": "Control",
"label": "Birth Date",
"scope": "#/properties/birthDate"
}
]
}
```

### Description display

The Keyword `descriptionDisplay` in Control options allows to specify the
display of the description. Possible options:

* `after`
* `before`
* `invisible`
* `tooltip`

The first three options are standard options available for the
`#description_display` in Drupal.

The option `tooltip` leads to an additional CSS class on the description
element: `json-forms-description-tooltip`. This can be used to process it
with another module to display the description as tooltip.

With the module [Form Tips](https://www.drupal.org/project/formtips) it can
be achieved with this CSS selector:

```css
:not(.json-forms-description-tooltip)
```

## Limitations

Some things cannot be done with (standard) Drupal forms, e.g.
[Rules](https://jsonforms.io/docs/uischema/rules/) cannot completely be mapped
to [conditional form fields](https://www.drupal.org/docs/drupal-apis/form-api/conditional-form-fields).

The [`detail` option](https://jsonforms.io/docs/uischema/controls#the-detail-option)
for controls referencing an array is unsupported.

TODO: Describe all limitations.
26 changes: 12 additions & 14 deletions composer.json
Original file line number Diff line number Diff line change
@@ -9,6 +9,11 @@
"email": "info@systopia.de"
}
],
"extra": {
"branch-alias": {
"dev-main": "0.6.x-dev"
}
},
"autoload": {
"psr-4": {
"Drupal\\json_forms\\": "src/"
@@ -24,27 +29,20 @@
"config": {
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
"dealerdirect/phpcodesniffer-composer-installer": true,
"php-http/discovery": false,
"phpstan/extension-installer": false,
"tbachert/spi": false
}
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/systopia/opis-json-schema-ext"
},
{
"type": "vcs",
"url": "https://github.com/systopia/expression-language-ext"
}
],
"require": {
"php": "^7.4 || ^8",
"systopia/expression-language-ext": "~0.1",
"systopia/opis-json-schema-ext": "~0.2"
},
"require-dev": {
"drupal/core": "^9.1.6",
"drupal/core-dev": "^9.1.6"
"drupal/core": "^9.5 || ^10",
"drupal/core-dev": "^9.5 || ^10"
},
"scripts": {
"composer-phpstan": [
@@ -60,7 +58,7 @@
"@php vendor/bin/phpcbf"
],
"phpstan": [
"@php tools/phpstan/vendor/bin/phpstan"
"@php tools/phpstan/vendor/bin/phpstan -v"
],
"phpunit": [
"@php vendor/bin/phpunit --coverage-text"
44 changes: 44 additions & 0 deletions js/disable-buttons-on-ajax.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2024 SYSTOPIA GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* Adding/removing entries to an array before a previous AJAX call has been
* finished might lead to an inconsistent state. Thus, buttons are disabled
* during AJAX calls. Additionally, form submit is not possible during AJAX
* calls. Fields that initiate an AJAX calls are disabled until the call is
* finished and would be missing in the submitted data.
*/
(function ($) {

$(document).on('ajaxStart', () => {
const buttons = document.querySelectorAll('input[type="submit"]:enabled, input[type="button"]:enabled');
buttons.forEach((button) => {
button.disabled = true;
button.setAttribute('data-ajax-disabled', 'true');
});
});

$(document).on('ajaxStop', () => {
const buttons = document.querySelectorAll('input[data-ajax-disabled="true"]');
buttons.forEach((button) => {
button.disabled = false;
button.removeAttribute('data-ajax-disabled');
});

});

})(jQuery);
124 changes: 124 additions & 0 deletions js/number-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (C) 2025 SYSTOPIA GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

/**
* Restricts input of number fields to digits, decimal separator, and minus.
*/
(function (Drupal, once) {
Drupal.behaviors.json_forms_numberInput = {
attach: function (context, settings) {
// Decimal separator of browser.
const decimalSeparator = Intl.NumberFormat()
.formatToParts(1.1)
.find(part => part.type === 'decimal')
.value;

const digits = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
];

const allowedKeys = [
'ArrowDown',
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'Backspace',
'Delete',
'End',
'Enter',
'Home',
'PageDown',
'PageUp',
'Tab',
];

function getElementLang(element) {
if (element.lang) {
return element.lang;
}

return element.parentElement ? getElementLang(element.parentElement) : null;
}

once('json-forms-number-input', 'input[type="number"]', context).forEach((element) => {
let decimalSeparators = decimalSeparator;
const lang = getElementLang(element);
if (lang) {
// Decimal separator of element's language might be different from
// the browser's separator. We allow both in that case. It depends on
// the browser which one is preferred, i.e. the one that is used when
// pressing the buttons to change a number.
decimalSeparators += Intl.NumberFormat(lang)
.formatToParts(1.1)
.find(part => part.type === 'decimal')
.value;
}

function isCharAllowed(char) {
if (digits.includes(char)) {
return true;
}

if ('-' === char) {
if (!element.value.includes('-') && (element.min < 0 || element.min === '' || element.min == null)) {
return true;
}
}
else if (decimalSeparators.includes(char)) {
if (!element.value.includes('.') && (element.step === 'any' || element.step < 1)) {
return true;
}
}

return false;
}

element.addEventListener('keydown', function (event) {
if (!event.ctrlKey && !allowedKeys.includes(event.key) && !isCharAllowed(event.key)) {
event.preventDefault();
}
});

element.addEventListener('paste', function (event) {
const data = event.clipboardData.getData('text/plain');
if (data === '') {
event.preventDefault();
return;
}

for (let i = 0; i < data.length; i++) {
if (!isCharAllowed(data[i])) {
event.preventDefault();

return;
}
}
});
});
}
};

})(Drupal, once);
52 changes: 0 additions & 52 deletions js/submit.js

This file was deleted.

Loading