UNPKG

@5minds/node-red-dashboard-2-processcube-dynamic-form

Version:
764 lines (645 loc) 33.3 kB
<script type="text/javascript"> RED.nodes.registerType('ui-dynamic-form', { category: 'ProcessCube UI', color: '#00aed7', defaults: { name: { value: '' }, group: { type: 'ui-group', required: true }, order: { value: 0 }, options: { value: [{ label: '' }], validate: function (v) { const unique = new Set( v.map(function (o) { return o.label; }) ); return v.length === unique.size; }, }, waiting_title: { value: 'Warten auf den Usertask...' }, waiting_info: { value: 'Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist.', }, form_columns: { value: 1, }, readonly: { value: false, }, collapsible: { value: false, }, collapse_when_finished: { value: false, }, actions_inside_card: { value: true, }, card_size_styling: { value: '', }, inner_card_styling: { value: '', }, title_text: { value: '' }, title_text_type: { value: 'str' }, title_style: { value: 'default', }, title_custom_text_styling: { value: '', }, title_icon: { value: '', }, trigger_on_change: { value: false, }, handle_confirmation_dialogs: { value: false, }, confirm_actions: { value: [ { label: 'Confirm', primary: 'primary', alignment: 'right' }, { label: 'Decline', primary: 'primary', alignment: 'right' }, ], }, width: { value: 0, validate: function (v) { const width = v || 0; const currentGroup = $('#node-input-group').val() || this.group; const groupNode = RED.nodes.node(currentGroup); const valid = !groupNode || +width <= +groupNode.width; $('#node-input-size').toggleClass('input-error', !valid); return valid; }, }, height: { value: 0 }, outputs: { value: 1 }, }, inputs: 1, outputs: 0, outputLabels: function (index) { const labels = [...this.options.map((i) => i.label)]; if (this.handle_confirmation_dialogs && this.confirm_actions) { labels.push('Decline (from UserTask)', 'Confirm (from UserTask)'); } if (this.trigger_on_change) { labels.push('Triggered on change'); } return labels[index]; }, icon: 'ui_dynamic_form.svg', label: function () { return this.name || 'dynamic-form'; }, oneditprepare: function () { $('#node-input-size').elementSizer({ width: '#node-input-width', height: '#node-input-height', group: '#node-input-group', }); $('#node-input-title_style').typedInput({ types: [ { value: 'title_style', options: [ { value: 'default', label: 'Default' }, { value: 'minimal', label: 'Minimal' }, { value: 'outside', label: 'Outside of card' }, ], }, ], }); $('#node-input-title_text').typedInput({ default: 'str', types: ['str', 'msg'], typeField: '#node-input-title_text_type', }); function generateOption(i, option) { const row = $('<tr/>', { style: 'background: var(--red-ui-secondary-background, #fff);', }); const labelCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); $('<input/>', { class: 'node-input-option-label', type: 'text', style: 'width: 100%; box-sizing: border-box; border: 1px solid var(--red-ui-form-input-border-color, #ccc); padding: 3px;', placeholder: 'Label', value: option.label, }).appendTo(labelCell); const conditionCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); const conditionInput = $('<input/>', { class: 'node-input-option-condition', type: 'text', style: 'width: 100%; box-sizing: border-box; border: 1px solid var(--red-ui-form-input-border-color, #ccc); padding: 3px;', placeholder: 'Condition', value: option.condition, }).appendTo(conditionCell); conditionInput.data('explicitly-cleared', false); conditionInput.on('input', function () { if ($(this).val().trim() === '') { $(this).data('explicitly-cleared', true); } else { $(this).data('explicitly-cleared', false); } }); const errorCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); $('<input/>', { class: 'node-input-option-error', type: 'text', style: 'width: 100%; box-sizing: border-box; border: 1px solid var(--red-ui-form-input-border-color, #ccc); padding: 3px;', placeholder: 'Error message', value: option.errorMessage, }).appendTo(errorCell); const alignmentCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); $('<input/>', { class: 'node-input-option-alignment', type: 'text', style: 'width: 100%; box-sizing: border-box;', placeholder: 'Alignment', value: option.alignment, }) .appendTo(alignmentCell) .typedInput({ types: [ { value: 'alignment', options: [ { value: 'left', label: 'Align left' }, { value: 'right', label: 'Align right' }, ], }, ], }); const typeCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); $('<input/>', { class: 'node-input-option-primary', type: 'text', style: 'width: 100%; box-sizing: border-box;', placeholder: 'Action type', value: option.primary, }) .appendTo(typeCell) .typedInput({ types: [ { value: 'type', options: [ { value: 'primary', label: 'Primary action' }, { value: 'secondary', label: 'Secondary action' }, { value: 'destructive', label: 'Destructive action' }, ], }, ], }); const deleteCell = $('<td/>', { style: 'padding: 4px; text-align: center; width: 30px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'height: 28px; display: inline-flex; align-items: center; justify-content: center;', }).appendTo(deleteCell); $('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton); deleteButton.click(function () { row.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)', }); row.fadeOut(300, function () { $(this).remove(); checkForDuplicateLabels(); }); }); $('#node-input-option-container tbody').append(row); row.find('.node-input-option-label').on('input', checkForDuplicateLabels); } function checkForDuplicateLabels() { const labels = []; let hasDuplicates = false; $('#node-input-option-container tbody .node-input-option-label').each(function () { const label = $(this).val().trim(); if (label && labels.includes(label)) { hasDuplicates = true; } if (label) { labels.push(label); } }); if (hasDuplicates) { $('#valWarning').show(); } else { $('#valWarning').hide(); } } $('#node-input-add-option').click(function () { const newOption = {}; if ($('#node-input-handle_confirmation_dialogs').is(':checked')) { newOption.condition = '!usertask.isConfirmDialog'; } generateOption($('#node-input-option-container tbody tr').length + 1, newOption); $('#node-input-option-container-div').scrollTop($('#node-input-option-container-div').get(0).scrollHeight); checkForDuplicateLabels(); }); for (let i = 0; i < this.options.length; i++) { const option = this.options[i]; generateOption(i + 1, option); } $('#node-input-option-container tbody .node-input-option-condition').each(function () { const conditionInput = $(this); if (!conditionInput.val().trim()) { conditionInput.data('explicitly-cleared', true); } }); checkForDuplicateLabels(); $('#node-input-option-container tbody').sortable({ axis: 'y', cursor: 'move', helper: function (e, tr) { var $originals = tr.children(); var $helper = tr.clone(); $helper.children().each(function (index) { $(this).width($originals.eq(index).width()); }); return $helper; }, }); function generateConfirmOption(option, index) { const row = $('<tr/>', { style: 'background: var(--red-ui-secondary-background, #fff);', }); const labelCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); $('<input/>', { class: 'node-input-confirm-option-label', type: 'text', style: 'width: 100%; box-sizing: border-box; background-color: var(--red-ui-form-input-background-disabled, #f9f9f9); border: 1px solid var(--red-ui-form-input-border-color, #ccc); padding: 3px;', placeholder: 'Label', value: option.label, readonly: true, }).appendTo(labelCell); const alignmentCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); $('<input/>', { class: 'node-input-confirm-option-alignment', type: 'text', style: 'width: 100%; box-sizing: border-box;', placeholder: 'Alignment', value: option.alignment, }) .appendTo(alignmentCell) .typedInput({ types: [ { value: 'alignment', options: [ { value: 'left', label: 'Align left' }, { value: 'right', label: 'Align right' }, ], }, ], }); const primaryCell = $('<td/>', { style: 'padding: 4px; border-bottom: 1px solid var(--red-ui-form-input-border-color, #eee);', }).appendTo(row); $('<input/>', { class: 'node-input-confirm-option-primary', type: 'text', style: 'width: 100%; box-sizing: border-box;', placeholder: 'Action type', value: option.primary, }) .appendTo(primaryCell) .typedInput({ types: [ { value: 'type', options: [ { value: 'primary', label: 'Primary action' }, { value: 'secondary', label: 'Secondary action' }, { value: 'destructive', label: 'Destructive action' }, ], }, ], }); $('#node-input-confirm-option-container tbody').append(row); } const confirmActions = this.confirm_actions || [ { label: 'Confirm', primary: 'primary', alignment: 'right' }, { label: 'Decline', primary: 'primary', alignment: 'right' }, ]; for (let i = 0; i < confirmActions.length; i++) { const option = confirmActions[i]; generateConfirmOption(option, i); } function toggleConfirmActionsSection() { const isChecked = $('#node-input-handle_confirmation_dialogs').is(':checked'); $('#confirm-actions-section').toggle(isChecked); } function handleConfirmationDialogToggle() { const isChecked = $('#node-input-handle_confirmation_dialogs').is(':checked'); if (isChecked) { $('#node-input-option-container tbody .node-input-option-condition').each(function () { const conditionInput = $(this); if (!conditionInput.val().trim() && !conditionInput.data('explicitly-cleared')) { conditionInput.val('!usertask.isConfirmDialog'); } }); } else { $('#node-input-option-container tbody .node-input-option-condition').each(function () { const conditionInput = $(this); if (conditionInput.val().trim() === '!usertask.isConfirmDialog') { conditionInput.val(''); conditionInput.data('explicitly-cleared', false); } }); } } toggleConfirmActionsSection(); $('#node-input-handle_confirmation_dialogs').on('change', function () { toggleConfirmActionsSection(); handleConfirmationDialogToggle(); }); }, oneditsave: function () { const options = $('#node-input-option-container tbody tr'); const node = this; node.options = []; options.each(function (i) { const row = $(this); const o = { label: row.find('.node-input-option-label').val(), condition: row.find('.node-input-option-condition').val().trim(), errorMessage: row.find('.node-input-option-error').val(), alignment: row.find('.node-input-option-alignment').val(), primary: row.find('.node-input-option-primary').val(), }; node.options.push(o); }); const confirmOptions = $('#node-input-confirm-option-container tbody tr'); node.confirm_actions = []; if (confirmOptions.length > 0) { confirmOptions.each(function (i) { const row = $(this); const o = { label: row.find('.node-input-confirm-option-label').val(), alignment: row.find('.node-input-confirm-option-alignment').val(), primary: row.find('.node-input-confirm-option-primary').val(), }; node.confirm_actions.push(o); }); } else { node.confirm_actions = [ { label: 'Confirm', primary: 'primary', alignment: 'right' }, { label: 'Decline', primary: 'primary', alignment: 'right' }, ]; } this.outputs = node.options.length || 0; if ($('#node-input-trigger_on_change').is(':checked')) { this.outputs++; } if ($('#node-input-handle_confirmation_dialogs').is(':checked')) { this.outputs += node.confirm_actions.length; } }, }); </script> <script type="text/html" data-template-name="ui-dynamic-form"> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <input type="text" id="node-input-name" placeholder="Name"> </div> <div class="form-row"> <label for="node-input-group"><i class="fa fa-table"></i> Group</label> <input type="text" id="node-input-group"> </div> <div class="form-row"> <label><i class="fa fa-object-group"></i> <span data-i18n="ui-dynamic-form.label.size"></label> <input type="hidden" id="node-input-width"> <input type="hidden" id="node-input-height"> <button class="editor-button" id="node-input-size"></button> </div> <h4>Action Configuration</h4> <div class="form-row form-row-flex node-input-option-container-row" style="margin-bottom: 0px;width: 100%"> <label for="node-input-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> Actions</label> <div id="node-input-option-container-div" style="box-sizing:border-box; border-radius:5px; height:287px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:scroll; display:inline-block; width: 70%;"> <span id="valWarning" style="color: var(--red-ui-text-color-error, #910000); display: none;"><b>Duplicate labels found! All action labels must be unique.</b></span> <table id="node-input-option-container" style="width: 100%; border-collapse: collapse; margin: 0;"> <thead> <tr style="background: var(--red-ui-primary-background, #f3f3f3);"> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);">Label</th> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);">Condition</th> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc);">Error Message</th> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc); width: 100px;">Align</th> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc); width: 120px;">Type</th> <th style="padding: 5px; text-align: center; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc); width: 30px;"></th> </tr> </thead> <tbody></tbody> </table> </div> </div> <div class="form-row"> <a href="#" class="editor-button editor-button-small" id="node-input-add-option" style="margin-top:4px; margin-left:120px;"><i class="fa fa-plus"></i> <span>Add action</span></a> </div> <div class="form-row"> <label for="node-input-handle_confirmation_dialogs"><i class="fa fa-hand"></i>Handle confirmation dialogs</label> <input type="checkbox" id="node-input-handle_confirmation_dialogs" title="Adds more outputs at this node for handling confirm/decline actions"> </div> <div class="form-row form-row-flex node-input-confirm-option-container-row" id="confirm-actions-section" style="margin-bottom: 0px;width: 100%; display: none;"> <label for="node-input-confirm-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> Confirm Actions</label> <div id="node-input-confirm-option-container-div" style="box-sizing:border-box; border-radius:5px; height:150px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:auto; display:inline-block; width: 70%;"> <table id="node-input-confirm-option-container" style="width: 100%; border-collapse: collapse; margin: 0;"> <thead> <tr style="background: var(--red-ui-primary-background, #f3f3f3);"> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc); width: 150px;">Label</th> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc); width: 120px;">Align</th> <th style="padding: 5px; text-align: left; font-size: 11px; font-weight: bold; border-bottom: 1px solid var(--red-ui-form-input-border-color, #ccc); width: 140px;">Type</th> </tr> </thead> <tbody></tbody> </table> </div> </div> <div class="form-row"> <label for="node-input-actions_inside_card"><i class="fa fa-hand"></i>Inside of card</label> <input type="checkbox" id="node-input-actions_inside_card"> </div> <hr /> <h4>Title Configuration</h4> <div class="form-row"> <label for="node-input-title_text"><i class="fa fa-hand"></i>Title text</label> <input type="text" id="node-input-title_text" title="Enter the text to show as a title."> <input type="hidden" id="node-input-title_text_type"> </div> <div class="form-row"> <label for="node-input-title_style"><i class="fa fa-hand"></i>Style</label> <input type="text" id="node-input-title_style" title="Select the base styling layout for your title."> </div> <div class="form-row"> <label for="node-input-title_custom_text_styling"><i class="fa fa-hand"></i>Custom text styling</label> <input type="text" id="node-input-title_custom_text_styling" title="Use CSS properties like font-size, color or padding to influence the appearance of the title"> </div> <div class="form-row"> <label for="node-input-title_icon"><i class="fa fa-hand"></i>Title icon</label> <input type="text" id="node-input-title_icon" title="Enter an URL to fetch an icon from. Use a dataurl for images, that are not available online. This value will be used as a HTML <img>'s src value."> </div> <hr /> <h4>Appearance</h4> <div class="form-row"> <label for="node-input-card_size_styling"><i class="fa fa-hand"></i>Card size styling</label> <input type="text" id="node-input-card_size_styling" title="Use CSS properties like margin, padding or max-width to influence sizing and positioning of the component."> </div> <div class="form-row"> <label for="node-input-inner_card_styling"><i class="fa fa-hand"></i>Inner card size styling</label> <input type="text" id="node-input-inner_card_styling" title="Use CSS properties like max-height to influence sizing and positioning of the components inner part, that contains only the form fields."> </div> <div class="form-row"> <label for="node-input-form_columns"><i class="fa fa-hand"></i>Columns in form</label> <input type="number" min="1" id="node-input-form_columns" title="Control the amount of columns, that your form fields should be placed in."> </div> <div class="form-row"> <label for="node-input-readonly"><i class="fa fa-hand"></i>Readonly</label> <input type="checkbox" id="node-input-readonly" title="Makes all form fields read only."> </div> <div class="form-row"> <label for="node-input-collapsible"><i class="fa fa-hand"></i>Collapsible</label> <input type="checkbox" id="node-input-collapsible" title="When checked, the card can be folded at any time."> </div> <div class="form-row"> <label for="node-input-collapse_when_finished"><i class="fa fa-hand"></i>Collapsible when finished</label> <input type="checkbox" id="node-input-collapse_when_finished" title="When checked, the card will automatically collapse after finishing the form."> </div> <div class="form-row"> <label for="node-input-waiting_title"><i class="fa fa-hand"></i>Title for waiting text</label> <input type="text" id="node-input-waiting_title" title="Displayed while no UserTask is present."> </div> <div class="form-row"> <label for="node-input-waiting_info"><i class="fa fa-hand"></i>Text for waiting text</label> <input type="text" id="node-input-waiting_info" title="Displayed while no UserTask is present."> </div> <hr /> <h4>Other</h4> <div class="form-row"> <label for="node-input-trigger_on_change"><i class="fa fa-hand"></i>Trigger on change</label> <input type="checkbox" id="node-input-trigger_on_change" title="A new output will be added. Each time a value inside the form changes, this output will be triggered."> </div> </script> <script type="text/markdown" data-help-name="ui-dynamic-form"> A UI-Node to display Forms for UserTasks from the ProcessCube Engine. ## States This component can have one of three states. `Waiting`, `active` and `finished`. - `Waiting` is the initial state. This node will either display the waiting text and waiting title, or nothing. This dependes on your configuration. This state can be set at any time by passing a message with an empty payload into this node. - `Active` will display a dynamic form for a UserTask. This state can be set by passing the result of the `usertask-input` or `wait-for-usertask` node into it. - `Finished` will display the last filled form in a disabled mode. This state can be set by passing the result of the `dynamic-form` node into itself. ## Usage Connect the input to the `usertask-input` or `wait-for-usertask` node. It will use the UserTasks FormFields configuration to display a dynamically generated Form inside a Card. After configuring an action, connect it's output to an `usertask-output` node. In case you want to display the `finished` state, place a `link in` and a `link out` node to pipe the `dynamic-form` nodes output into its input. In case you want to clear the node and set it back into `waiting` state, place a `function` node that clears the `payload` object. The pipe the `dynamic-form` nodes output into the `function` node and the `function` nodes output into th e`dynamic-form` nodes input. ## Action Configuration Actions are buttons displayed at the bottom of the Form. ### Actions A Form can have any amount of actions. Each action is specified with a: - **Label**: The buttons text. - **Condition (optional)**: An expression that evaluates to true or false. It controls whether the action is displayed and enabled. - **Error message (optional)**: Will be displayed, when Condition evaluates to false. - **Alignment**: Controls the buttons position. It can be placed on the left or right side of the form. - **Action type**: Controls the styling of the button. There should always be one primary action. For each action configured, the node will gain a new output, that is named as the linked action. #### Condition Various values can be accessed inside the condition through following constants: Access to the message object is available through _msg_ Access to the form fields is available through the _fields_ object. A single form field can be accessed by selecting the field id as a key. Example: _fields.field_01_ Access to the usertask object is available through _usertask_. The usertask object includes a special property _usertask.isConfirmDialog_ that indicates whether the current form is displaying a confirmation dialog. **Examples:** - `!usertask.isConfirmDialog` - Show action only in normal forms, not in confirmation dialogs - `usertask.isConfirmDialog` - Show action only in confirmation dialogs - `fields.myField === 'someValue'` - Show action only when a specific field has a certain value ### Handles confirmation dialogs When checked, this node will gain two additional outputs 'Decline' and 'Confirm'. These are required when handling confirmation dialogs that may be defined in the Studio by setting a Form Field's type to 'Confirm'. When a 'Confirm' FormField is present and this option is enabled, only the confirmation buttons (Decline/Confirm) will be displayed by default. Regular actions will only be shown if their condition evaluates to true in the confirmation dialog context. The default condition `!usertask.isConfirmDialog` ensures that regular actions are hidden in confirmation dialogs but shown in normal forms. You can customize this behavior by modifying the condition for each action. ### Confirm Actions Configuration When "Handle confirmation dialogs" is enabled, a dedicated configuration section appears for customizing the confirmation buttons. Unlike regular actions, confirm actions are automatically generated based on the UserTask's field configuration but can be styled and positioned according to your needs. #### Confirm Actions Table The confirm actions table provides configuration options for: - **Label**: Display text for the button (read-only). This value comes directly from the UserTask's field configuration - **Alignment**: Controls button positioning within the form - **Type**: Controls the visual styling of the button - Note: The label cannot be changed here, as it is sourced from the UserTask configuration #### Default Confirm Action Configuration When first enabled, the system creates two default confirm actions: - **Confirm**: Primary button, right-aligned - **Decline**: Primary button, right-aligned #### Behavior with Confirmation Dialogs When a UserTask contains a FormField with type 'Confirm': 1. The form automatically switches to confirmation dialog mode 2. Regular actions are filtered based on their conditions (using `usertask.isConfirmDialog`) 3. Only the configured confirm actions are displayed 4. Button labels are overridden by the UserTask's field configuration 5. The outputs route to "Decline (from UserTask)" and "Confirm (from UserTask)" respectively #### Condition Interaction Confirm actions work in conjunction with regular action conditions: - Regular actions with empty conditions are shown in both normal and confirmation dialogs - Actions with `!usertask.isConfirmDialog` are hidden in confirmation dialogs - Actions with `usertask.isConfirmDialog` are only shown in confirmation dialogs - This allows for flexible control over which actions appear in different contexts ### Inside of card This property controls the positioning of the action buttons. They can be palced inside of the card, right below the FormFields, or below the whole Card containing the Form. ## Title Configuration ### Title The text displayed above the FormFields in the Cards header. When left empty, there will be no header in the Card. ### Style Controls the headers styling. `Default` is the recommended 5Minds theming. `Minimal` might be used to apply custom stylings and `Outside of card` places the title right above the card. ### Custom text styling Enter custom css rules to modifiy how the title text is displayed. This can be used particulary well together with the style `Minimal`. ### Title icon An URL for an image, that should be displayed before the text. This will be used as the `src`-Property of a HTML `img`-Element. Therefore DataURLs can be used to add images/svgs etc., that are not available online. ## Appearance ### Card size styling Enter custom css rules to control the size and placement of the whole card. It can be used to set a `max-width` for the Card, apply a `margin` etc. ### Inner card size styling Enter custom css rules to control the size of the inner part, that contains just the form fields. It can be used to override the `max-height` property. E.g. `max-height: none;` will remove the default `max-height` of 550px, so that the card can grow indefinitely. ### Columns in form This value controls the amount of columns the card will have. It can be used to place multiple FormFields in one row. Configure `hidden` FormFields in your UserTask configuration to leave spaces open between FormFields. FormFields with type `heading` will always take up a whole line regardless of the `Columns in Form` value. ### Readonly When checked, all fields in the Form will not be editable. To mark single FormFields as readOnly, add the custom property `readOnly` with the value `true` to the FormField configuration of the UserTask. ### Collapsible When checked, the card will have a collapse icon and can be folded at any. ### Collapsible when finished When checked, the card will gain a collapse icon and automatically fold, after the node entered `finished` state. This is independed to `Collapsible`. ### Title for waiting text While no UserTask is present, this text will be displayed. ### Text for waiting text While no UserTask is present, this text will be displayed. ## Other ### Trigger on change Adds a new output to the node. This output will be triggered each time a FormFields value changes. This can be used for live, server-side validation or for Forms without action buttons. ## References - [The ProcessCube Developer Network](https://processcube.io) - All documentation for the ProcessCube&copy; Plattform - [Node-RED Integration in ProcessCube&copy;](https://processcube.io/docs/node-red) - Node-RED Integration in ProcessCube&copy; </script>