[Bps-public-commit] rt-extension-formtools branch dynamic-forms-from-config updated. 0.53-100-ga0acc98

BPS Git Server git at git.bestpractical.com
Tue Nov 7 20:55:14 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt-extension-formtools".

The branch, dynamic-forms-from-config has been updated
       via  a0acc982528c2977ecdd311b78baa65fe3a82359 (commit)
      from  f42ab019e8dc72643ea69bab14baa12148502802 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit a0acc982528c2977ecdd311b78baa65fe3a82359
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Nov 7 15:51:35 2023 -0500

    Support client side dependent validation
    
    Currently only "required" is supported.

diff --git a/html/Admin/FormTools/Modify.html b/html/Admin/FormTools/Modify.html
index 12747e9..98d22fc 100644
--- a/html/Admin/FormTools/Modify.html
+++ b/html/Admin/FormTools/Modify.html
@@ -138,8 +138,8 @@
             </a>
           </p>
         </div>
-        <div class="modal fade formtools-element-modal" id="formtools-element-<% CSSClass($item) %>-modal" tabindex="-1" role="dialog">
-          <div class="modal-dialog" role="document">
+        <div class="modal fade formtools-element-modal" id="formtools-element-<% CSSClass($item) %>-modal" data-type="component" data-name="<% $item %>" tabindex="-1" role="dialog">
+          <div class="modal-dialog <% %dependent_custom_fields ? 'modal-lg' : '' %>" role="document">
             <div class="modal-content">
               <form class="formtools-element-form">
                 <div class="modal-header">
@@ -166,6 +166,37 @@
                       </label>
                     </div>
                   </&>
+%                 if ( %dependent_custom_fields ) {
+                  <&| /Elements/LabeledValue, Label => '' &>
+                    <div class="custom-control custom-checkbox">
+                      <input class="custom-control-input" id="<% CSSClass($item) %>-depedent-validation" type="checkbox" name="dependent_validation" value="1" />
+                      <label class="custom-control-label has-text-input" for="<% CSSClass($item) %>-depedent-validation">
+                        <&|/l&>Show validation if</&>
+                        <div class="d-inline-block">
+                          <select name="dependent_name" class="form-control">
+                            <option value=""><&|/l&>(no value)</&></option>
+%                         for my $name ( sort keys %dependent_custom_fields ) {
+                            <option value="<% $name %>"><% $name %></option>
+%                         }
+                          </select>
+                        </div>
+                        is
+                        <div class="d-inline-block">
+                          <select name="dependent_value" data-dependent-name="dependent_name" multiple class="form-control">
+                            <option value=""><&|/l&>(no value)</&></option>
+%                         for my $name ( sort keys %dependent_custom_fields ) {
+                            <optgroup label="<% $name %>">
+%                           for my $value ( @{$dependent_custom_fields{$name}} ) {
+                              <option value="<% $value %>"><% $value %></option>
+%                           }
+                            </optgroup>
+%                         }
+                          </select>
+                        </div>
+                      </label>
+                    </div>
+                  </&>
+%                 }
                   <&| /Elements/LabeledValue, Label => '' &>
                     <div class="custom-control custom-checkbox">
                       <input class="custom-control-input" id="<% CSSClass($item) %>-hide" type="checkbox" name="hide" value="1" />
@@ -353,8 +384,8 @@
           <div class="modal-wrapper">
 %         $i = 0;
 %         for my $item ( grep { $_->{type} ne 'hidden' || $_->{'input-name'} ne 'create_ticket' } @{$form->{'formtools-pages'}{$page_name}{content} || []} ) {
-            <div class="modal fade formtools-element-modal" id="formtools-element-<% $form_page_id{$page_name} %>-<% $i %>-modal" tabindex="-1" role="dialog">
-              <div class="modal-dialog" role="document">
+            <div class="modal fade formtools-element-modal" id="formtools-element-<% $form_page_id{$page_name} %>-<% $i %>-modal" tabindex="-1" role="dialog" data-type="<% $item->{type} %>" data-name="<% $item->{arguments} && $item->{arguments}{name} ? $item->{arguments}{name} : '' %>">
+              <div class="modal-dialog <% $item->{type} eq 'component' && %dependent_custom_fields && !RT::Extension::FormTools::is_core_field($item->{arguments}{name}) ? 'modal-lg' : '' %>" role="document">
                 <div class="modal-content">
                   <form class="formtools-element-form">
                     <div class="modal-header">
@@ -403,6 +434,37 @@
                           </label>
                         </div>
                       </&>
+%                     if ( %dependent_custom_fields ) {
+                      <&| /Elements/LabeledValue, Label => '' &>
+                        <div class="custom-control custom-checkbox">
+                          <input class="custom-control-input" id="formtools-element-<% $form_page_id{$page_name} %>-<% $i %>-depedent-validation" type="checkbox" name="dependent_validation" value="1" <% $item->{arguments}{dependent_validation}{enabled} ? 'checked="checked"' : '' |n %> />
+                          <label class="custom-control-label has-text-input" for="formtools-element-<% $form_page_id{$page_name} %>-<% $i %>-depedent-validation">
+                            <&|/l&>Show validation if</&>
+                            <div class="d-inline-block">
+                              <select name="dependent_name" class="form-control selectpicker">
+                                <option value=""><&|/l&>(no value)</&></option>
+%                               for my $name ( sort keys %dependent_custom_fields ) {
+                                  <option value="<% $name %>" <% ($item->{arguments}{dependent_validation}{name} // '') eq $name ? 'selected="selected"' : '' |n %>><% $name %></option>
+%                               }
+                                </select>
+                              </div>
+                              is
+                              <div class="d-inline-block">
+                                <select name="dependent_value" data-dependent-name="dependent_name" multiple class="form-control selectpicker">
+                                  <option value=""><&|/l&>(no value)</&></option>
+%                               for my $name ( sort keys %dependent_custom_fields ) {
+                                  <optgroup label="<% $name %>">
+%                                 for my $value ( @{$dependent_custom_fields{$name}} ) {
+                                    <option value="<% $value %>" <% ($item->{arguments}{dependent_validation}{name} // '') eq $name && grep( { $value eq $_ } @{$item->{arguments}{dependent_validation}{values} || []} ) ? 'selected="selected"' : '' |n %>><% $value %></option>
+%                                 }
+                                  </optgroup>
+%                               }
+                                </select>
+                              </div>
+                            </label>
+                          </div>
+                        </&>
+%                     }
 %                     }
 
                       <&| /Elements/LabeledValue, Label => '' &>
@@ -472,6 +534,34 @@ jQuery(function() {
             event.returnValue = true;
         }
     });
+
+    jQuery('[name=dependent_name]').change(function () {
+        const modal = jQuery(this).closest('.modal');
+        const target = modal.find('[data-dependent-name="' + jQuery(this).attr('name') + '"');
+        const name = jQuery(this).val();
+        target.find('optgroup').addClass('hidden');
+        target.find('optgroup[label="' + name + '"]').removeClass('hidden');
+        target.selectpicker('refresh');
+        // Hide duplicate dividers
+        target.closest('div').find('li.dropdown-divider').slice(1).hide();
+    });
+
+    jQuery('.formtools-element-modal').on('shown.bs.modal', function (e) {
+        const name = jQuery(this).attr('data-name');
+
+        const select = jQuery(this).find('select[name=dependent_name]');
+
+        select.find('option').addClass('hidden');
+        jQuery('.formtools-form-pages [data-type=component][data-name]').each(function () {
+            const available_name = jQuery(this).attr('data-name');
+            if ( available_name !== name ) {
+                select.find('option[value="' + available_name + '"]').removeClass('hidden');
+            }
+        });
+
+        select.change();
+        select.selectpicker('refresh');
+    });
 });
 </script>
 <%INIT>
@@ -579,6 +669,7 @@ my $cfs = $queue->TicketCustomFields;
 my @custom_fields;
 my %default_values;
 my %tooltips;
+my %dependent_custom_fields;
 
 while ( my $cf = $cfs->Next ) {
     push @custom_fields, $cf->Name;
@@ -588,6 +679,10 @@ while ( my $cf = $cfs->Next ) {
         }
     }
     $tooltips{$cf->Name} = $cf->EntryHint // '';
+
+    if ( $cf->Type eq 'Select' ) {
+        $dependent_custom_fields{$cf->Name} = [ map { $_->Name } @{$cf->Values->ItemsArrayRef || {}} ];
+    }
 }
 
 my %other_components = (
diff --git a/html/FormTools/Field b/html/FormTools/Field
index 91cbccb..98127eb 100644
--- a/html/FormTools/Field
+++ b/html/FormTools/Field
@@ -145,8 +145,24 @@ $default = '' unless defined $default;
 % if ($render_as ne 'hidden' && $show_label) { # no label if hidden
 
 <&| /Elements/LabeledValue, RawLabel => $m->interp->apply_escapes($field_label, 'h') . $after_label, LabelSpanClass => $tooltip ? 'prev-icon-helper' : '', LabelTooltip => $tooltip &>
-  <& SELF:Value, %ARGS, input_name => $input_name, field_type => $field_type, default => $default, values => \@values, cf => $cf &>
-  <% $after_input |n %>
+  <div data-name="<% $cf ? $cf->Name : '' %>"
+% if ( $ARGS{dependent_validation} && $ARGS{dependent_validation}{enabled} && $ARGS{dependent_validation}{name} ) {
+%   my $dependent_cf = RT::CustomField->new($session{CurrentUser});
+%   $dependent_cf = RT::CustomField->new( $session{'CurrentUser'} );
+%   $dependent_cf->SetContextObject($ticket) if $cf->can('SetContextObject');
+
+%   # try loading CFs for this Queue, followed by Global, followed by any CF of given $name
+%   $dependent_cf->LoadByName( Name => $ARGS{dependent_validation}{name}, Queue => $queue->id ) if (defined $queue);
+%   $dependent_cf->LoadByName( Name => $ARGS{dependent_validation}{name}, Queue => 0 ) unless ( $cf->id );
+%   if ( $dependent_cf ) {
+    data-dependent-name="<% GetCustomFieldInputName( Object => $ticket, CustomField => $dependent_cf ) %>"
+    data-dependent-values="<% JSON($ARGS{dependent_validation}{values} || []) %>"
+%   }
+% }
+  >
+    <& SELF:Value, %ARGS, input_name => $input_name, field_type => $field_type, default => $default, values => \@values, cf => $cf &>
+    <% $after_input |n %>
+  </div>
 </&>
 
 % } else {
diff --git a/html/Forms/dhandler b/html/Forms/dhandler
index 7b88305..da768ba 100644
--- a/html/Forms/dhandler
+++ b/html/Forms/dhandler
@@ -35,6 +35,61 @@ foreach my $element ( @{$form_config->{'formtools-pages'}{$page}{'content'}} ) {
 % if ( $form_config->{'formtools-pages'}{$page}{'next'} ) {
  <& /FormTools/Next, Label => $button_label, Back => $show_back, Name => 'Submit' &>
 % }
+
+
+<script type="text/javascript">
+jQuery(function () {
+    jQuery('[data-dependent-name]').each(function () {
+        const dependent_name = jQuery(this).attr('data-dependent-name');
+        const values = JSON.parse(jQuery(this).attr('data-dependent-values'));
+        const target = jQuery(this);
+        const syncValidation = function(source) {
+            if ( source.target ) {
+                source = jQuery(source.target);
+            }
+
+            let source_values = [];
+            const name = source.attr('name');
+            if ( source.is(':checkbox') || source.is(':radio') ) {
+                jQuery(':input[name="' + name +'"]:checked').each( function () {
+                    source_values.push(source.val());
+                });
+            }
+            else {
+                jQuery(':input[name="' + name +'"]').each(function () {
+                    const val = jQuery(this).val();
+                    if ( Array.isArray(val) ) {
+                        source_values.push(...val);
+                    }
+                    else {
+                        source_values.push(val);
+                    }
+                });
+            }
+            let matched;
+            for ( let source_value of source_values ) {
+                if ( values.includes(source_value) ) {
+                    matched = true;
+                }
+            }
+
+            if ( matched ) {
+                target.find(':input:visible').attr('required', true);
+            }
+            else {
+                target.find(':input:visible').attr('required', false);
+            }
+        };
+        jQuery(':input[type!=hidden][name="' + dependent_name +'"]').change(syncValidation).change();
+
+        // Handle the case where dependent input is on previous pages
+        if ( jQuery(':input[type=hidden][name="' + dependent_name +'"]').length ) {
+            syncValidation(jQuery(':input[type=hidden][name="' + dependent_name +'"]'));
+        }
+    });
+});
+</script>
+
 </&>
 <%init>
 
diff --git a/static/css/rt-extension-formtools.css b/static/css/rt-extension-formtools.css
index 27152c7..33f4132 100644
--- a/static/css/rt-extension-formtools.css
+++ b/static/css/rt-extension-formtools.css
@@ -80,3 +80,8 @@ div .formtools-admin-description {
 .formtools-element-empty-room {
     height: 200px;
 }
+
+#formtools-edit .custom-control-label.has-text-input::before,
+#formtools-edit .custom-control-label.has-text-input::after {
+  top: 0.5rem;
+}
diff --git a/static/js/rt-extension-formtools.js b/static/js/rt-extension-formtools.js
index 9391d31..3a38734 100644
--- a/static/js/rt-extension-formtools.js
+++ b/static/js/rt-extension-formtools.js
@@ -148,6 +148,30 @@ formTools = {
                 }
             }
 
+
+            const dependent_validation = form.find(':input[name=dependent_validation]');
+            if ( dependent_validation.length ) {
+                value.arguments.dependent_validation ||= {};
+                if ( dependent_validation.is(':checked') ) {
+                    value.arguments.dependent_validation.enabled = 1;
+                }
+                else {
+                    value.arguments.dependent_validation.enabled = 0;
+                }
+            }
+
+            const dependent_name = form.find(':input[name=dependent_name]');
+            if ( dependent_name.length ) {
+                value.arguments.dependent_validation ||= {};
+                value.arguments.dependent_validation.name = dependent_name.val();
+            }
+
+            const dependent_value = form.find(':input[name=dependent_value]');
+            if ( dependent_value.length ) {
+                value.arguments.dependent_validation ||= {};
+                value.arguments.dependent_validation.values = dependent_value.val();
+            }
+
             const hide = form.find(':input[name=hide]');
             if ( hide.length ) {
                 if ( hide.is(':checked') ) {

-----------------------------------------------------------------------

Summary of changes:
 html/Admin/FormTools/Modify.html      | 103 ++++++++++++++++++++++++++++++++--
 html/FormTools/Field                  |  20 ++++++-
 html/Forms/dhandler                   |  55 ++++++++++++++++++
 static/css/rt-extension-formtools.css |   5 ++
 static/js/rt-extension-formtools.js   |  24 ++++++++
 5 files changed, 201 insertions(+), 6 deletions(-)


hooks/post-receive
-- 
rt-extension-formtools


More information about the Bps-public-commit mailing list