Skip to content

Commit

Permalink
Merge branch 'master' into FIO-7954-fixed-translations
Browse files Browse the repository at this point in the history
  • Loading branch information
TanyaGashtold authored Dec 30, 2024
2 parents d07386c + 13c8270 commit 9ecd7b5
Show file tree
Hide file tree
Showing 21 changed files with 3,116 additions and 1,634 deletions.
29 changes: 28 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,34 @@ This library is a plain JavaScript form renderer and SDK for Form.io. This allow
- Complete Form Builder which creates the JSON schema used to render the forms.
- Nested components, layouts, Date/Time, Select, Input Masks, and many more included features
- Full JavaScript API SDK library on top of Form.io


## Important Updates

### Namespace Change

Starting with version 5.x, this library has moved to a new namespace. Please update your npm install command as follows:

`npm install --save @formio/js`

If you are upgrading from version 4.x, please ensure you update your imports and dependencies to use the new namespace @formio/js.

### CDN update

Our cdn endpoints also reflect the changes to the new namespace when retrieving versions >4.x:

Example:
- https://cdn.form.io/js/formio.full.min.js
- https://cdn.form.io/js/5.0.0/formio.full.min.js

### Maintenance Mode for Version 4.x

Version 4.x of this library is now in maintenance mode. This means:

- No new features will be added to version 4.x.
- Only bug fixes and security updates will be provided.

For the latest features and improvements, we strongly recommend upgrading to version 5.x.

## Examples and Demonstration
To find out more about this library as well as see a demonstration of what you can do with this library, go to the Examples and Demo site @ [https://formio.github.io/formio.js](https://formio.github.io/formio.js)

Expand Down
70 changes: 54 additions & 16 deletions src/components/_classes/component/Component.js
Original file line number Diff line number Diff line change
Expand Up @@ -374,11 +374,19 @@ export default class Component extends Element {
// Needs for Nextgen Rules Engine
this.resetCaches();

/**
* Determines if this component is conditionally hidden. Should generally not be set outside of conditional logic pipeline.
* This is necessary because of clearOnHide behavior that only clears when conditionally hidden - we need to track
* conditionallyHidden separately from "regular" visibility.
*/
this._parentConditionallyHidden = this.options.hasOwnProperty('parentConditionallyHidden') ? this.options.parentConditionallyHidden : false;
this._conditionallyHidden = this.checkConditionallyHidden(null, data) || this._parentConditionallyHidden;

/**
* Determines if this component is visible, or not.
*/
this._parentVisible = this.options.hasOwnProperty('parentVisible') ? this.options.parentVisible : true;
this._visible = this._parentVisible && this.conditionallyVisible(null, data);
this._visible = this._parentVisible && (this.hasCondition() ? !this._conditionallyHidden : !this.component.hidden);
this._parentDisabled = false;

/**
Expand Down Expand Up @@ -454,7 +462,7 @@ export default class Component extends Element {
if (this.allowData && this.key) {
this.options.name += `[${this.key}]`;
// If component is visible or not set to clear on hide, set the default value.
if (this.visible || !this.component.clearOnHide) {
if (!(this.conditionallyHidden && this.component.clearOnHide)) {
if (!this.hasValue()) {
if (this.shouldAddDefaultValue) {
this.dataValue = this.defaultValue;
Expand Down Expand Up @@ -537,7 +545,8 @@ export default class Component extends Element {

init() {
this.disabled = this.shouldDisabled;
this._visible = this.conditionallyVisible(null, null);
this._conditionallyHidden = this.checkConditionallyHidden();
this._visible = (this.hasCondition() ? !this.conditionallyHidden : !this.component.hidden);
if (this.component.addons?.length) {
this.component.addons.forEach((addon) => this.createAddon(addon));
}
Expand Down Expand Up @@ -709,7 +718,6 @@ export default class Component extends Element {
return;
}
this._visible = value;
this.clearOnHide();
this.redraw();
}
}
Expand All @@ -732,6 +740,23 @@ export default class Component extends Element {
return this._visible && this._parentVisible;
}

get conditionallyHidden() {
return this._conditionallyHidden || this._parentConditionallyHidden;
}

/**
* Evaluates whether the component is conditionally hidden (as opposed to intentionally hidden, e.g. via the `hidden` component schema property).
* @param {object} data - The data object to evaluate the condition against.
* @param {object} row - The row object to evaluate the condition against.
* @returns {boolean} - Whether the component is conditionally hidden.
*/
checkConditionallyHidden(data = null, row = null) {
if (!this.hasCondition()) {
return false;
}
return !this.conditionallyVisible(data, row);
}

get currentForm() {
return this._currentForm;
}
Expand Down Expand Up @@ -2060,7 +2085,7 @@ export default class Component extends Element {
rebuild() {
this.destroy();
this.init();
this.visible = this.conditionallyVisible(null, null);
this.visible = this.hasCondition() ? !this.conditionallyHidden : !this.component.hidden;
return this.redraw();
}

Expand Down Expand Up @@ -2137,8 +2162,8 @@ export default class Component extends Element {
conditionallyVisible(data, row) {
data = data || this.rootValue;
row = row || this.data;
if (this.builderMode || this.previewMode || !this.hasCondition()) {
return !this.component.hidden;
if (this.builderMode || this.previewMode) {
return true;
}
data = data || (this.root ? this.root.data : {});
return this.checkCondition(row, data);
Expand Down Expand Up @@ -2178,8 +2203,15 @@ export default class Component extends Element {
this.redraw();
}

// Check advanced conditions
const visible = this.conditionallyVisible(data, row);
// Check advanced conditions (and cache the result)
const isConditionallyHidden = this.checkConditionallyHidden(data, row) || this._parentConditionallyHidden;
if (isConditionallyHidden !== this._conditionallyHidden) {
this._conditionallyHidden = isConditionallyHidden;
this.clearOnHide();
}

// Check visibility
const visible = (this.hasCondition() ? !this.conditionallyHidden : !this.component.hidden);

if (this.visible !== visible) {
this.visible = visible;
Expand Down Expand Up @@ -2321,6 +2353,12 @@ export default class Component extends Element {

const property = action.property.value;
if (!_.isEqual(_.get(this.component, property), _.get(newComponent, property))) {
// Advanced Logic can modify the component's hidden property; because we track conditionally hidden state
// separately from the component's hidden property, and technically this Advanced Logic conditionally hides
// a component, we need to set _conditionallyHidden to the new value
if (property === 'hidden') {
this._conditionallyHidden = newComponent.hidden;
}
changed = true;
}

Expand All @@ -2339,7 +2377,7 @@ export default class Component extends Element {
}
);

if (!_.isEqual(oldValue, newValue) && !(this.component.clearOnHide && !this.visible)) {
if (!_.isEqual(oldValue, newValue) && !(this.component.clearOnHide && this.conditionallyHidden)) {
this.setValue(newValue);

if (this.viewOnly) {
Expand Down Expand Up @@ -2384,7 +2422,7 @@ export default class Component extends Element {
},
'value');

if (!_.isEqual(oldValue, newValue) && !(this.component.clearOnHide && !this.visible)) {
if (!_.isEqual(oldValue, newValue) && !(this.component.clearOnHide && this.conditionallyHidden)) {
this.setValue(newValue);

if (this.viewOnly) {
Expand Down Expand Up @@ -2513,7 +2551,7 @@ export default class Component extends Element {
!this.options.readOnly &&
!this.options.showHiddenFields
) {
if (!this.visible) {
if (this.conditionallyHidden) {
this.deleteValue();
}
else if (!this.hasValue() && this.shouldAddDefaultValue) {
Expand Down Expand Up @@ -2808,7 +2846,7 @@ export default class Component extends Element {
get dataValue() {
if (
!this.key ||
(!this.visible && this.component.clearOnHide && !this.rootPristine)
(this.conditionallyHidden && this.component.clearOnHide && !this.rootPristine)
) {
return this.emptyValue;
}
Expand All @@ -2830,7 +2868,7 @@ export default class Component extends Element {
if (
!this.allowData ||
!this.key ||
(!this.visible && this.component.clearOnHide && !this.rootPristine)
(this.conditionallyHidden && this.component.clearOnHide && !this.rootPristine)
) {
return;
}
Expand Down Expand Up @@ -3194,7 +3232,7 @@ export default class Component extends Element {
// If no calculated value or
// hidden and set to clearOnHide (Don't calculate a value for a hidden field set to clear when hidden)
const { clearOnHide } = this.component;
const shouldBeCleared = !this.visible && clearOnHide;
const shouldBeCleared = this.conditionallyHidden && clearOnHide;
const allowOverride = _.get(this.component, 'allowCalculateOverride', false);

if (shouldBeCleared) {
Expand Down Expand Up @@ -3918,7 +3956,7 @@ export default class Component extends Element {
// If component definition changed, replace it.
if (!_.isEqual(this.component, newComponent)) {
this.component = newComponent;
const visible = this.conditionallyVisible(null, null);
const visible = this.hasCondition() ? !this.conditionallyHidden : !this.component.hidden;
const disabled = this.shouldDisabled;

// Change states which won't be recalculated during redrawing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ export default [
{
weight: 700,
type: 'checkbox',
label: 'Clear Value When Hidden',
label: 'Omit Value From Submission Data When Conditionally Hidden',
key: 'clearOnHide',
defaultValue: true,
tooltip: 'When a field is hidden, clear the value.',
tooltip: 'When a field is conditionally hidden, omit the value from the submission data.',
input: true
},
EditFormUtils.javaScriptValue('Custom Default Value', 'customDefaultValue', 'customDefaultValue', 1000,
Expand Down
24 changes: 17 additions & 7 deletions src/components/_classes/nested/NestedComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,27 +86,36 @@ export default class NestedComponent extends Field {
const visibilityChanged = this._visible !== value;
this._visible = value;
const isVisible = this.visible;
const isConditionallyHidden = this.checkConditionallyHidden();
const forceShow = this.shouldForceShow();
const forceHide = this.shouldForceHide();
this.components.forEach(component => {
this.components.forEach((component) => {
// Set the parent visibility first since we may have nested components within nested components
// and they need to be able to determine their visibility based on the parent visibility.
component.parentVisible = isVisible;
component._parentConditionallyHidden = isConditionallyHidden;
let visible;
if (component.hasCondition()) {
component._conditionallyHidden = component.checkConditionallyHidden() || component._parentConditionallyHidden;
visible = !component.conditionallyHidden;
}
else {
visible = !component.component.hidden;
}

const conditionallyVisible = component.conditionallyVisible();
if (forceShow || conditionallyVisible) {
if (forceShow || visible) {
component.visible = true;
}
else if (forceHide || !isVisible || !conditionallyVisible) {
else if (forceHide || !isVisible || !visible ) {
component.visible = false;
}
// If hiding a nested component, clear all errors below.
if (!component.visible) {
component.error = '';
}
});

if (visibilityChanged) {
this.clearOnHide();
this.redraw();
}
}
Expand Down Expand Up @@ -399,6 +408,7 @@ export default class NestedComponent extends Field {
data = data || this.data;
options.parent = this;
options.parentVisible = this.visible;
options.parentConditionallyHidden = this.conditionallyHidden;
options.root = options?.root || this.root || this;
options.localRoot = this.localRoot;
options.skipInit = true;
Expand Down Expand Up @@ -688,7 +698,7 @@ export default class NestedComponent extends Field {
clearOnHide(show) {
super.clearOnHide(show);
if (this.component.clearOnHide) {
if (this.allowData && !this.hasValue() && !(this.options.server && !this.visible)) {
if (this.allowData && !this.hasValue() && !this.conditionallyHidden) {
this.dataValue = this.defaultValue;
}
if (this.hasValue()) {
Expand Down Expand Up @@ -721,7 +731,7 @@ export default class NestedComponent extends Field {

calculateValue(data, flags, row) {
// Do not iterate into children and calculateValues if this nested component is conditionally hidden.
if (!this.conditionallyVisible()) {
if (this.conditionallyHidden) {
return false;
}
return this.getComponents().reduce(
Expand Down
2 changes: 1 addition & 1 deletion src/components/datamap/DataMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default class DataMapComponent extends DataGridComponent {
get dataValue() {
if (
!this.key ||
(!this.visible && this.component.clearOnHide)
(this.conditionallyHidden && this.component.clearOnHide)
) {
return this.emptyValue;
}
Expand Down
7 changes: 2 additions & 5 deletions src/components/editgrid/EditGrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -1359,7 +1359,7 @@ export default class EditGridComponent extends NestedArrayComponent {
}

const changed = this.hasChanged(value, this.dataValue);
if (this.parent && !this.options.server) {
if (this.parent) {
this.parent.checkComponentConditions();
}
this.dataValue = value;
Expand Down Expand Up @@ -1394,10 +1394,7 @@ export default class EditGridComponent extends NestedArrayComponent {

this.openWhenEmpty();
this.updateOnChange(flags, changed);
// do not call checkData with server option, it is called when change is triggered in updateOnChange
if (!this.options.server) {
this.checkData();
}
this.checkData();

this.changeState(changed, flags);

Expand Down
19 changes: 11 additions & 8 deletions src/components/form/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ export default class FormComponent extends Component {

// Make sure to not show the submit button in wizards in the nested forms.
_.set(options, 'buttonSettings.showSubmit', false);


// Set the parent option to the subform so those references are stable when the subform is created
options.parent = this;

if (!this.options) {
return options;
}
Expand Down Expand Up @@ -440,12 +443,11 @@ export default class FormComponent extends Component {
return (new Form(form, this.getSubOptions())).ready.then((instance) => {
this.subForm = instance;
this.subForm.currentForm = this;
this.subForm.parentVisible = this.visible;
const componentsMap = this.componentsMap;
const formComponentsMap = this.subForm.componentsMap;
_.assign(componentsMap, formComponentsMap);
this.component.components = this.subForm.components.map((comp) => comp.component);
this.subForm.parent = this;
this.subForm.parentVisible = this.visible;
this.component.components = this.subForm.components.map((comp) => comp.component);
this.subForm.on('change', () => {
if (this.subForm) {
this.dataValue = this.subForm.getValue();
Expand Down Expand Up @@ -473,11 +475,12 @@ export default class FormComponent extends Component {
}

hideSubmitButton(component) {
const isSubmitButton = (component.type === 'button') &&
((component.action === 'submit') || !component.action);
const isSubmitButton = component.type === 'button' && (component.action === 'submit' || !component.action);

if (isSubmitButton) {
component.hidden = true;
// clearOnHide no longer clears from the JSON `hidden` flag, so we make the button conditionally hidden to clear its data
component.customConditional = 'show = false';
}
}

Expand All @@ -487,7 +490,7 @@ export default class FormComponent extends Component {
* @returns {Promise} - The promise that resolves when the subform is loaded.
*/
loadSubForm(fromAttach) {
if (this.builderMode || this.isHidden() || (this.isSubFormLazyLoad() && !fromAttach)) {
if (this.builderMode || this.conditionallyHidden || (this.isSubFormLazyLoad() && !fromAttach)) {
return Promise.resolve();
}

Expand Down Expand Up @@ -569,7 +572,7 @@ export default class FormComponent extends Component {
* @returns {*|boolean} - TRUE if the subform should be submitted, FALSE if it should not.
*/
get shouldSubmit() {
return this.subFormReady && (!this.component.hasOwnProperty('reference') || this.component.reference) && !this.isHidden();
return this.subFormReady && (!this.component.hasOwnProperty('reference') || this.component.reference) && !this.conditionallyHidden;
}

/**
Expand Down
Loading

0 comments on commit 9ecd7b5

Please sign in to comment.