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

BPS Git Server git at git.bestpractical.com
Wed Oct 4 21:48:41 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  1aa048178a00e9dcec566f4232796cdff9c7ead5 (commit)
      from  c8a2ec6d9c733d1270db113ea053d796d65d81fa (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 1aa048178a00e9dcec566f4232796cdff9c7ead5
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 4 17:25:24 2023 -0400

    Initial version of generally working form builder
    
    The following features have been implemented:
    
    * Add/Delete pages
    
    * Update page metadata including name, sort order, and validation.
    
    * Add/Delete/Update/Reorder fields for each page, including basic html
      elements, core fields, custom fields, and special fields(Hidden and
      ShowChoices)

diff --git a/META.yml b/META.yml
index 9f86295..3cfe80b 100644
--- a/META.yml
+++ b/META.yml
@@ -21,6 +21,7 @@ no_index:
     - inc
     - static
 requires:
+  UUID::Tiny: 0
   perl: 5.10.1
 resources:
   license: http://opensource.org/licenses/gpl-license.php
diff --git a/Makefile.PL b/Makefile.PL
index ed71e84..0ae9903 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -1,6 +1,7 @@
 use inc::Module::Install;
 RTx('RT-Extension-FormTools');
 requires_rt('5.0.0');
+requires('UUID::Tiny');
 
 repository('https://github.com/bestpractical/rt-extension-formtools');
 
diff --git a/html/Admin/FormTools/Modify.html b/html/Admin/FormTools/Modify.html
index 95e40dd..338b55a 100644
--- a/html/Admin/FormTools/Modify.html
+++ b/html/Admin/FormTools/Modify.html
@@ -8,51 +8,362 @@
     <&| /Widgets/TitleBox, title => loc('FormTools Components') &>
       <div class="d-block text-center">
 % foreach my $item ( @html_components ) {
-    <p id="formtools-element-<% $item %>" class="m-1 p-2 border border-primary rounded" draggable="true" ondragstart="dragstart_handler(event);" ondragend="dragend_handler(event);"><% uc($item) %> Element</p>
+        <div id="formtools-element-<% $item %>" class="formtools-element" draggable="true" ondragstart="formTools.dragstart(event);" ondragend="formTools.dragend(event);" data-value="<% JSON({ type => 'raw_html', html => "<$item>test</$item>"}) %>">
+          <p class="formtools-element-placeholder m-1 p-2 border rounded"><% loc('Put it here') %></p>
+          <p class="m-1 p-2 border rounded">
+            <span class="badge badge-primary">raw_html</span>
+            <span class="content"><% loc('[_1] Element', uc($item)) %></span>
+%           if ( $item ne 'hr' ) {
+            <a href="#" class="edit" data-toggle="modal" data-target="#formtools-element-modal">
+              <span class="fas fa-pencil-alt" alt="<% loc('Edit') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Edit') %>"></span>
+            </a>
+%           }
+            <a href="#" class="remove" onclick="jQuery(this).find('[data-toggle=tooltip]').tooltip('hide'); this.closest('.formtools-element').remove(); return false;">
+              <span class="far fa-times-circle" alt="<% loc('Remove') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Remove') %>"></span>
+            </a>
+          </p>
+%         if ( $item ne 'hr' ) {
+          <div class="modal fade formtools-element-modal" id="formtools-element-<% $item %>-modal" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+              <div class="modal-content">
+                <form class="formtools-element-form">
+                  <div class="modal-header">
+                    <h5 class="modal-title"><% loc('Modify Element') %></h5>
+                    <a href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">×</span>
+                    </a>
+                  </div>
+                  <div class="modal-body">
+                    <textarea name="html" class="form-control"><% "<$item>Text</$item>" %></textarea>
+                  </div>
+                  <div class="modal-footer">
+                    <button type="submit" class="btn btn-primary button form-control"><% loc('Save') %></button>
+                  </div>
+                </form>
+              </div>
+            </div>
+          </div>
+%         }
+        </div>
 % }
-      <hr />
+        <hr />
 % foreach my $item ( @core_components ) {
-    <p id="formtools-element-<% $item %>" class="m-1 p-2 border border-primary rounded" draggable="true" ondragstart="dragstart_handler(event);" ondragend="dragend_handler(event);"><% ucfirst($item) %></p>
+        <div id="formtools-element-<% $item %>" class="formtools-element" draggable="true" ondragstart="formTools.dragstart(event);" ondragend="formTools.dragend(event);" data-value="<% JSON({ type => 'component', comp_name => 'Field', arguments => { name => $item } }) %>">
+          <p class="formtools-element-placeholder m-1 p-2 border rounded"><% loc('Put it here') %></p>
+          <p class="m-1 p-2 border rounded">
+            <span class="badge badge-primary">component</span>
+            <% $item %>
+            <a href="#" class="edit" data-toggle="modal" data-target="#formtools-element-modal">
+              <span class="fas fa-pencil-alt" alt="<% loc('Edit') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Edit') %>"></span>
+            </a>
+            <a href="#" class="remove" onclick="jQuery(this).find('[data-toggle=tooltip]').tooltip('hide'); this.closest('.formtools-element').remove(); return false;">
+              <span class="far fa-times-circle" alt="<% loc('Remove') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Remove') %>"></span>
+            </a>
+          </p>
+          <div class="modal fade formtools-element-modal" id="formtools-element-<% $item %>-modal" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+              <div class="modal-content">
+                <form class="formtools-element-form">
+                  <div class="modal-header">
+                    <h5 class="modal-title"><% loc('Modify Element') %></h5>
+                    <a href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">×</span>
+                    </a>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-row">
+                      <div class="col-3 label"><&|/l&>Label</&>:</div>
+                      <div class="col-9 value">
+                        <input name="label" type="text" class="form-control" placeholder="<% $item %>" value="" />
+                      </div>
+                    </div>
+                    <div class="form-row">
+                      <div class="col-3 label"><&|/l&>Default Value</&>:</div>
+                      <div class="col-9 value">
+                        <input name="default" type="text" class="form-control" placeholder="<% $default_values{$item} %>" value="" />
+                      </div>
+                    </div>
+                  </div>
+                  <div class="modal-footer">
+                    <button type="submit" class="btn btn-primary button form-control"><% loc('Save') %></button>
+                  </div>
+                </form>
+              </div>
+            </div>
+          </div>
+        </div>
 % }
-      <hr />
+        <hr />
 % foreach my $item ( @custom_fields ) {
-    <p id="formtools-element-<% $item %>" class="m-1 p-2 border border-primary rounded" draggable="true" ondragstart="dragstart_handler(event);" ondragend="dragend_handler(event);"><% uc($item) %></p>
+        <div id="formtools-element-<% $item %>" class="formtools-element" draggable="true" ondragstart="formTools.dragstart(event);" ondragend="formTools.dragend(event);" data-value="<% JSON({ type => 'component', comp_name => 'Field', arguments => { name => $item } }) %>">
+          <p class="formtools-element-placeholder m-1 p-2 border rounded"><% loc('Put it here') %></p>
+          <p class="m-1 p-2 border rounded">
+            <span class="badge badge-primary">component</span>
+            <% $item %>
+            <a href="#" class="edit" data-toggle="modal" data-target="#formtools-element-modal">
+              <span class="fas fa-pencil-alt" alt="<% loc('Edit') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Edit') %>"></span>
+            </a>
+            <a href="#" class="remove" onclick="jQuery(this).find('[data-toggle=tooltip]').tooltip('hide'); this.closest('.formtools-element').remove(); return false;">
+              <span class="far fa-times-circle" alt="<% loc('Remove') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Remove') %>"></span>
+            </a>
+          </p>
+          <div class="modal fade formtools-element-modal" id="formtools-element-<% $item %>-modal" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+              <div class="modal-content">
+                <form class="formtools-element-form">
+                  <div class="modal-header">
+                    <h5 class="modal-title"><% loc('Modify Element') %></h5>
+                    <a href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">×</span>
+                    </a>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-row">
+                      <div class="col-3 label"><&|/l&>Label</&>:</div>
+                      <div class="col-9 value">
+                        <input name="label" type="text" class="form-control" placeholder="<% $item %>" value="" />
+                      </div>
+                    </div>
+                    <div class="form-row">
+                      <div class="col-3 label"><&|/l&>Default Value</&>:</div>
+                      <div class="col-9 value">
+                        <input name="default" type="text" class="form-control" placeholder="<% $default_values{$item} %>" value="" />
+                      </div>
+                    </div>
+                  </div>
+                  <div class="modal-footer">
+                    <button type="submit" class="btn btn-primary button form-control"><% loc('Save') %></button>
+                  </div>
+                </form>
+              </div>
+            </div>
+          </div>
+        </div>
+% }
+
+        <hr />
+% foreach my $item ( sort keys %other_components ) {
+        <div id="formtools-element-<% $item %>" class="formtools-element" draggable="true" ondragstart="formTools.dragstart(event);" ondragend="formTools.dragend(event);" data-value="<% JSON($other_components{$item}) %>">
+          <p class="formtools-element-placeholder m-1 p-2 border rounded"><% loc('Put it here') %></p>
+          <p class="m-1 p-2 border rounded">
+            <span class="badge badge-primary"><% $other_components{$item}{type} %></span>
+            <span class="content"><% $item %></span>
+%         if ( $item eq 'Hidden' ) {
+            <a href="#" class="edit" data-toggle="modal" data-target="#formtools-element-modal">
+              <span class="fas fa-pencil-alt" alt="<% loc('Edit') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Edit') %>"></span>
+            </a>
+%         }
+            <a href="#" class="remove" onclick="jQuery(this).find('[data-toggle=tooltip]').tooltip('hide'); this.closest('.formtools-element').remove(); return false;">
+              <span class="far fa-times-circle" alt="<% loc('Remove') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Remove') %>"></span>
+            </a>
+          </p>
+
+%         if ( $item eq 'Hidden' ) {
+          <div class="modal fade formtools-element-modal" id="formtools-element-<% $item %>-modal" tabindex="-1" role="dialog">
+            <div class="modal-dialog" role="document">
+              <div class="modal-content">
+                <form class="formtools-element-form">
+                  <div class="modal-header">
+                    <h5 class="modal-title"><% loc('Modify Element') %></h5>
+                    <a href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+                      <span aria-hidden="true">×</span>
+                    </a>
+                  </div>
+                  <div class="modal-body">
+                    <div class="form-row">
+                      <div class="col-3 label"><&|/l&>Name</&>:</div>
+                      <div class="col-9 value">
+                        <input name="name" type="text" class="form-control" value="" />
+                      </div>
+                    </div>
+                    <div class="form-row">
+                      <div class="col-3 label"><&|/l&>Value</&>:</div>
+                      <div class="col-9 value">
+                        <input name="value" type="text" class="form-control" value="" />
+                      </div>
+                    </div>
+                  </div>
+                  <div class="modal-footer">
+                    <button type="submit" class="btn btn-primary button form-control"><% loc('Save') %></button>
+                  </div>
+                </form>
+              </div>
+            </div>
+          </div>
+%         }
+        </div>
 % }
       </div>
     </&>
   </div>
-<div class="formtools-form-pages boxcontainer col-md-9" id="formtools-pages-wrapper">
-<&| /Widgets/TitleBox, title => loc('FormTools Pages') &>
-% my @form_pages = keys %{$form->{'formtools-pages'}};
-  <ul class="nav nav-<% $nav_type %>s" id="formtools-pages">
-% my $current_context = {};
-% foreach my $page_name (@form_pages) {
-%     my $tab_id = CSSClass( $page_name );
-%     $current_context->{tab} = $tab_id;
-% #     my( $active, $aria_selected) = $tab_id eq $active_context->{tab} ? ('active', 'true') : ('', 'false');
-%     my( $active, $aria_selected) = ('active', 'true');
-%     my $nav_id = join '-', 'nav', $current_context->{tab};
-%     my $content_id = join '-', 'content', $current_context->{tab};
-    <li class="nav-item">
-      <a class="nav-link <% $active %>" id="<% $nav_id %>" data-toggle="<% $nav_type %>" href="#<% $content_id %>" role="<% $nav_type %>" aria-controls="<% $content_id %>" aria-selected="<% $aria_selected %>"><% $page_name %></a>
-    </li>
+  <div class="formtools-form-pages boxcontainer col-md-9" id="formtools-pages-wrapper">
+  <&| /Widgets/TitleBox, title => loc('FormTools Pages') &>
+
+    <ul class="nav nav-<% $nav_type %>s" id="formtools-pages">
+%   my $current_context = {};
+%   foreach my $page_name (@form_pages) {
+%     my( $active, $aria_selected) = $form_page_id{$page_name} eq $active_context->{tab} ? ('active', 'true') : ('', 'false');
+%     my $nav_id = join '-', 'formtools', 'nav', $form_page_id{$page_name};
+%     my $content_id = join '-', 'formtools', 'content', $form_page_id{$page_name};
+      <li class="nav-item">
+        <a class="nav-link <% $active %>" id="formtools-tab-<% $nav_id %>" data-toggle="<% $nav_type %>" href="#<% $content_id %>" role="<% $nav_type %>" aria-controls="formtools-<% $content_id %>" aria-selected="<% $aria_selected %>"><% $form->{'formtools-pages'}{$page_name}{name} %></a>
+      </li>
 % }
-  </ul>
-  <p>Drag components from the left toolbar and drop them here</p>
-    <div id="formtools-content-form-1" class="formtools-content w-100 border border-primary rounded" ondrop="drop_handler(event);" ondragover="dragover_handler(event);" ondragend="dragend_handler(event);">
+      <li class="nav-item">
+        <a class="nav-link" id="formtools-tab-add" href="?id=<% $id %>;AddPage=1">
+          <span class="fas fa-plus" alt="<% loc('Add Page') %>" data-toggle="tooltip" data-original-title="<% loc('Add Page') %>"></span>
+        </a>
+      </li>
+    </ul>
+    <div class="tab-content">
+%   foreach my $page_name (@form_pages) {
+      <div id="formtools-content-<% $form_page_id{$page_name} %>" class="tab-pane <% $form_page_id{$page_name} eq $active_context->{tab} ? 'show active' : 'fade' %>" role="tabpanel">
+        <&| /Widgets/TitleBox, title => loc('Basic Info'), titleright_raw => qq{} &>
+        <form>
+          <div class="form-row">
+            <div class="col-3 label"><&|/l&>Name</&>:</div>
+            <div class="col-9 value">
+              <input name="name" class="form-control" value="<% $form->{'formtools-pages'}{$page_name}{name} %>" />
+            </div>
+          </div>
+          <div class="form-row">
+            <div class="col-3 label"><&|/l&>Sort order</&>:</div>
+            <div class="col-9 value">
+              <input name="sort_order" class="form-control" value="<% $form->{'formtools-pages'}{$page_name}{sort_order} %>" />
+            </div>
+          </div>
+          <div class="form-row">
+            <div class="col-3 label"></div>
+            <div class="col-9 value">
+              <div class="custom-control custom-checkbox">
+                <input class="custom-control-input" id="<% $form_page_id{$page_name} %>-validation" type="checkbox" name="validation" value="1" <% $form->{'formtools-pages'}{$page_name}{validation} ? 'checked="checked"' : '' |n%> />
+                <label class="custom-control-label" for="<% $form_page_id{$page_name} %>-validation">
+                  <&|/l&>Enable validation</&>
+                </label>
+              </div>
+            </div>
+          </div>
+%         # Do not delete the last one
+%         if ( $form->{'formtools-pages'}{$page_name}{next} ) {
+          <div class="form-row">
+            <div class="col-3 label"></div>
+            <div class="col-9 value">
+              <a class="formtools-delete-page btn btn-primary button" data-page="<% $page_name %>" href="#"><% loc('Delete Page') %></a>
+            </div>
+          </div>
+%         }
+        </form>
+        </&>
+
+        <&| /Widgets/TitleBox, title => loc('Content') &>
+          <div class="formtools-content w-100 border rounded" data-page="<% $page_name %>" data-page-id="<% $form_page_id{$page_name} %>" ondrop="formTools.drop(event);" ondragover="formTools.dragover(event);" ondragend="formTools.dragend(event);">
+%         my $i = 0;
+%         for my $item ( grep { $_->{type} ne 'hidden' || $_->{'input-name'} ne 'create_ticket' } @{$form->{'formtools-pages'}{$page_name}{content} || []} ) {
+            <div id="formtools-element-<% $form_page_id{$page_name} %>-<% $i %>" class="formtools-element" draggable="true" ondragstart="formTools.dragstart(event);" ondragend="formTools.dragend(event);" ondragenter="formTools.dragenter(event)" ondragleave="formTools.dragleave(event)" data-value="<% JSON($item) %>">
+              <p class="formtools-element-placeholder m-1 p-2 border rounded"><% loc('Put it here') %></p>
+              <p class="m-1 p-2 border rounded">
+                <span class="badge badge-primary"><% $item->{type} %></span>
+                <span class="content">
+%               if ( $item->{type} eq 'raw_html' ) {
+                  <% length $item->{html} > 40 ? substr($item->{html}, 0, 40) . '...' : $item->{html} %>
+%               } elsif ( $item->{type} eq 'hidden' ) {
+                  <% $item->{'input-name'} %>: <% $item->{'input-value'} %>
+%               } else {
+                  <% $item->{arguments}{name} || $item->{comp_name} %>
+%               }
+                </span>
+%             if ( $item->{type} eq 'raw_html' || ( $item->{type} eq 'component' && $item->{comp_name} eq 'Field' ) || $item->{type} eq 'hidden' ) {
+                <a href="#" class="edit" data-toggle="modal" data-target="#formtools-element-<% $form_page_id{$page_name} %>-<% $i %>-modal">
+                  <span class="fas fa-pencil-alt" alt="<% loc('Edit') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Edit') %>"></span>
+                </a>
+%             }
+                <a href="#" class="remove" onclick="jQuery(this).find('[data-toggle=tooltip]').tooltip('hide'); this.closest('.formtools-element').remove(); return false;">
+                  <span class="far fa-times-circle" alt="<% loc('Remove') %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc('Remove') %>"></span>
+                </a>
+              </p>
+              <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-content">
+                    <form class="formtools-element-form">
+                      <div class="modal-header">
+                        <h5 class="modal-title"><% loc('Modify Element') %></h5>
+                        <a href="javascript:void(0)" class="close" data-dismiss="modal" aria-label="Close">
+                          <span aria-hidden="true">×</span>
+                        </a>
+                      </div>
+                      <div class="modal-body">
+%                     if ( $item->{type} eq 'raw_html' ) {
+                        <textarea name="html" class="form-control"><% $item->{html} %></textarea>
+%                     } elsif ( $item->{type} eq 'component' && $item->{comp_name} eq 'Field' ) {
+                        <div class="form-row">
+                          <div class="col-3 label"><&|/l&>Label</&>:</div>
+                          <div class="col-9 value">
+                            <input name="label" type="text" class="form-control" placeholder="<% $item->{arguments}{name} %>" value="<% $item->{arguments}{label} // ''  %>" />
+                          </div>
+                        </div>
+                        <div class="form-row">
+                          <div class="col-3 label"><&|/l&>Default Value</&>:</div>
+                          <div class="col-9 value">
+                            <input name="default" type="text" class="form-control" placeholder="<% $default_values{$item->{arguments}{name}} %>" value="<% $item->{arguments}{default} // '' %>" />
+                          </div>
+                        </div>
+%                     } elsif ( $item->{type} eq 'hidden' ) {
+                        <div class="form-row">
+                          <div class="col-3 label"><&|/l&>Name</&>:</div>
+                          <div class="col-9 value">
+                            <input name="name" type="text" class="form-control" value="<% $item->{'input-name'} // ''  %>" />
+                          </div>
+                        </div>
+                        <div class="form-row">
+                          <div class="col-3 label"><&|/l&>Value</&>:</div>
+                          <div class="col-9 value">
+                            <input name="value" type="text" class="form-control" value="<% $item->{'input-value'} // '' %>" />
+                          </div>
+                        </div>
+%                     }
+                      </div>
+                      <div class="modal-footer">
+
+                        <button type="submit" class="btn btn-primary button form-control"><% loc('Save') %></button>
+                      </div>
+                    </form>
+                  </div>
+                </div>
+              </div>
+            </div>
+%           $i++;
+%         }
+            <p class="formtools-element-placeholder m-1 p-2 border rounded"><% loc('Put it here') %></p>
+          </div>
+
+
+        </&>
+      </div>
+%   }
     </div>
-</&>
-</div><!-- row -->
+
+    <form method="POST" action="Modify.html" id="formtools-form-modify">
+      <input type="hidden" name="id" value="<% $id %>">
+      <input type="hidden" name="ActiveTab" value="">
+      <input type="hidden" name="Content">
+      <div class="row">
+        <div class="col-12" style="padding-right: 25px">
+          <& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
+        </div>
+      </div>
+    </form>
+  </&>
+  </div><!-- row -->
 </div><!-- formtools-form-pages -->
 </div><!-- formtools-edit -->
 
-<p><&|/l&>Loaded form <% $form_attribute->Description %></&></p>
-
-<pre>
-% use Data::Printer;
-% $m->out(np($form));
-</pre>
-
+<script type="text/javascript">
+jQuery(function() {
+    jQuery('#formtools-form-modify').on('submit', formTools.submit);
+    jQuery('.formtools-element-form').on('submit', formTools.elementSubmit);
+    jQuery('.formtools-delete-page').on('click', formTools.deletePage);
+});
+</script>
 <%INIT>
 
 Abort("No form id found") unless $id;
@@ -66,16 +377,146 @@ unless ( $ok ) {
 
 my $form = $form_attribute->Content;
 
-my ($title, @results);
-$title = loc("Modify form [_1]", $form_attribute->Description);
+use UUID::Tiny 'create_uuid_as_string';
+
+my @results;
+
+if ( $AddPage ) {
+    Abort( loc('Permission Denied') )
+        unless $session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'SuperUser' );
+
+    my @orders = map { $form->{'formtools-pages'}{$_}{sort_order} } keys %{$form->{'formtools-pages'}};
+
+    my $new_page = create_uuid_as_string();
+    $form->{'formtools-pages'}{$new_page} = {
+        name       => 'Page New',
+        sort_order => ( $orders[-2] || 0 ) + 1,
+    };
+    my ( $ret, $msg ) = $form_attribute->SetContent($form);
+    if ($ret) {
+        push @results, loc('Updated content');
+    }
+    else {
+        push @results, loc( "Couldn't update content: [_1]", $msg );
+    }
+
+    MaybeRedirectForResults(
+        Actions   => \@results,
+        Path      => '/Admin/FormTools/Modify.html',
+        Arguments => { id => $id, ActiveTab => $new_page },
+    );
+}
+elsif ( $Update ) {
+    Abort( loc('Permission Denied') )
+        unless $session{'CurrentUser'}->HasRight( Object => $RT::System, Right => 'SuperUser' );
+
+    my $new_content = eval { JSON::from_json( $ARGS{Content} ) };
+    if ( $@ ) {
+        push @results, loc( "Couldn't decode JSON" );
+    }
+    else {
+        require List::Util;
+        my %order_name = map { $new_content->{$_}{sort_order} => $_ } keys %$new_content;
+        my @orders = sort { $a <=> $b } keys %order_name;
+
+        my ( $first_page, $submit_page );
+        for my $page ( sort { $new_content->{$a}{sort_order} <=> $new_content->{$b}{sort_order} } keys %$new_content ) {
+            if ( $new_content->{$page}{sort_order} == $orders[0] ) {
+                $form->{'formtools-start-page'} = $page;
+            }
+
+            if ( $orders[-2] && $new_content->{$page}{sort_order} == $orders[-2] ) {
+                push @{ $new_content->{$page}{content} },
+                    { type => 'hidden', 'input-name' => 'create_ticket', 'input-value' => 'create_ticket' };
+            }
+
+            if ( my $next_order = List::Util::first { $_ > $new_content->{$page}{sort_order} } @orders ) {
+                $new_content->{$page}{next} = $order_name{$next_order};
+            }
+            else {
+                $new_content->{$page}{next} = '';
+            }
+        }
+
+        $form->{'formtools-pages'} = $new_content;
+
+        if ( $form_attribute->_SerializeContent($form) ne $form_attribute->_Value('Content') ) {
+            my ( $ret, $msg ) = $form_attribute->SetContent($form);
+            if ($ret) {
+                push @results, loc('Updated content');
+            }
+            else {
+                push @results, loc( "Couldn't update content: [_1]", $msg );
+            }
+        }
+    }
+
+    MaybeRedirectForResults(
+        Actions   => \@results,
+        Path      => '/Admin/FormTools/Modify.html',
+        Arguments => { id => $id, ActiveTab => $ActiveTab },
+    );
+}
+
+my $title = loc("Modify form [_1]", $form_attribute->Description);
 
 my $nav_type = 'pill'; # 'tab' or 'pill'
 
-my @html_components = qw( h1 h2 h3 hr );
-my @core_components = qw( requestors owner subject content );
-my @custom_fields = qw( cf1 cf2 cf3 );
+my @html_components = qw( h1 h2 h3 h4 h5 h6 hr p );
+my @core_components = qw( Requestors Owner Subject Content );
+
+my $queue = RT::Queue->new($session{'CurrentUser'});
+$queue->Load($form->{queue});
+my $cfs = $queue->TicketCustomFields;
+my @custom_fields;
+my %default_values;
+
+while ( my $cf = $cfs->Next ) {
+    push @custom_fields, $cf->Name;
+    if ( $cf->SupportDefaultValues ) {
+        if ( defined( my $default_values = $cf->DefaultValues(Object => $queue) ) ) {
+            $default_values{$cf->Name} = ref $default_values eq 'ARRAY' ? join(', ', @$default_values) : $default_values;
+        }
+    }
+}
+
+my %other_components = (
+    ShowChoices => { type => 'component', comp_name => 'ShowChoices' },
+    Hidden      => { type => 'hidden' },
+);
+
+
+
+$form->{'formtools-pages'} ||= {
+    create_uuid_as_string => { sort_order => 1, name => 'Page 1' },
+    create_uuid_as_string => {
+        sort_order => 999,
+        name       => 'Result',
+        content    => [
+            {
+                type => 'raw_html',
+                html => '<h2>Request Submitted</h2>',
+            },
+            {
+                type => 'raw_html',
+                html => '<p>Your request has been submitted.</p>',
+            },
+        ],
+    },
+};
+
+
+my @form_pages
+    = sort { ( $form->{'formtools-pages'}{$a}{sort_order} || 0 ) <=> ( $form->{'formtools-pages'}{$b}{sort_order} || 0 ) }
+    keys %{ $form->{'formtools-pages'} };
+
 
+my %form_page_id = map { $_ => CSSClass($_) } @form_pages;
+my $active_context = { tab => $ActiveTab || $form_page_id{$form_pages[0]} };
 </%INIT>
 <%ARGS>
 $id => undef
+$ActiveTab => ''
+$Update => undef
+$AddPage => undef
 </%ARGS>
diff --git a/static/css/rt-extension-formtools.css b/static/css/rt-extension-formtools.css
index ad60315..f9288f3 100644
--- a/static/css/rt-extension-formtools.css
+++ b/static/css/rt-extension-formtools.css
@@ -2,3 +2,44 @@
 div .formtools-content {
     min-height: 400px;
 }
+
+.formtools-form-pages .badge {
+    background-color: #4868b3;
+}
+
+.formtools-form-pages .remove {
+    position: absolute;
+    right: 2.5em;
+    color: #5C6273;
+}
+
+.formtools-component-menu .remove {
+    display: none;
+}
+
+.formtools-form-pages .edit {
+    position: absolute;
+    right: 4em;
+    color: #5C6273;
+}
+
+.formtools-component-menu .edit {
+    display: none;
+}
+
+.formtools-component-menu .badge {
+    display: none;
+}
+
+.formtools-element-placeholder {
+    border: 2px dotted #ffc107 !important;
+    display: none;
+}
+
+.formtools-element-placeholder.active {
+    display: block;
+}
+
+.formtools-element.current p {
+    opacity: 0.7;
+}
\ No newline at end of file
diff --git a/static/js/rt-extension-formtools.js b/static/js/rt-extension-formtools.js
index 31c5d6e..905714b 100644
--- a/static/js/rt-extension-formtools.js
+++ b/static/js/rt-extension-formtools.js
@@ -1,69 +1,147 @@
-function dragstart_handler(ev) {
-  console.log("dragStart");
-  var dti = ev.dataTransfer.items;
-  if (dti === undefined || dti == null) {
-    console.log("Browser does not support DataTransferItem interface");
-    return;
-  }
-
-  // Add the id of the drag source element to the drag data payload so
-  // it is available when the drop event is fired
-  dti.add(ev.target.id, "text/plain");
-  // Tell the browser both copy and move are possible
-  ev.effectAllowed = "copy";
-}
-
-function dragover_handler(ev) {
-    console.log("dragOver");
-    var dti = ev.dataTransfer.items;
-    if (dti === undefined || dti == null) {
-        console.log("Browser does not support DataTransferItem interface");
-        return;
-    }
-    // Change the target element's border to signify a drag over event
-    // has occurred
-    ev.currentTarget.style.background = "lightgray";
-    ev.preventDefault();
-}
-
-function drop_handler(ev) {
-    console.log("Drop");
-    ev.preventDefault();
-    var dti = ev.dataTransfer.items;
-    if (dti === undefined || dti == null) {
-        console.log("Browser does not support DataTransferItem interface");
-        return;
-    }
-    // Get the id of the drag source element (that was added to the drag data
-    // payload by the dragstart event handler). Even though only one drag item
-    // was explicitly added, the browser may include other items so need to search
-    // for the plain/text item.
-    for (var i=0; i < dti.length; i++) {
-        console.log("Drop: item[" + i + "].kind = " + dti[i].kind + " ; item[" + i + "].type = " + dti[i].type);
-        if ((dti[i].kind == 'string') && (dti[i].type.match('^text/plain'))) {
-          // This item is the target node
-          dti[i].getAsString(function (id){
-              // Copy the element
-              var nodeCopy = document.getElementById(id).cloneNode(true);
-              console.log("Copying " + nodeCopy);
-              nodeCopy.id = "newId";
-              ev.target.appendChild(nodeCopy);
-          });
+formTools = {
+    dragstart: function (ev) {
+        ev.dataTransfer.setData("text/plain", ev.target.id);
+        // Tell the browser both copy and move are possible
+        ev.effectAllowed = "copy";
+
+        jQuery(ev.target).addClass('current');
+        jQuery(ev.target).find('.formtools-element-placeholder').addClass('hidden');
+        jQuery(ev.target).next().find('.formtools-element-placeholder').addClass('hidden');
+    },
+
+    dragenter: function (ev) {
+        ev.preventDefault();
+        jQuery(ev.target).closest('.formtools-content').find('.formtools-element-placeholder').removeClass('active');
+        jQuery(ev.target).closest('.formtools-element').children('.formtools-element-placeholder').addClass('active');
+    },
+
+    dragleave: function (ev) {
+        ev.preventDefault();
+    },
+
+    dragover: function (ev) {
+        ev.preventDefault();
+        if ( ev.target.classList.contains('formtools-content') ) {
+            const last_element = jQuery(ev.target).find('.formtools-element').get(-1);
+            if ( last_element ) {
+                const last_position = last_element.getBoundingClientRect();
+                if ( ev.y > last_position.y + last_position.height ) {
+                    jQuery(ev.target).find('.formtools-element .formtools-element-placeholder').removeClass('active');
+                    jQuery(ev.target).children('.formtools-element-placeholder').addClass('active');
+                }
+            }
         }
-    }
+    },
 
-    // Clear background
-    ev.currentTarget.style.background = "none";
-}
+    drop: function (ev) {
+        ev.preventDefault();
 
-function dragend_handler(ev) {
-    console.log("dragEnd");
-    var dti = ev.dataTransfer.items;
-    if (dti === undefined || dti == null) {
-        console.log("Browser does not support DataTransferItem interface");
-        return;
-    }
+        const source = document.getElementById(ev.dataTransfer.getData("text"));
+
+        const sibling = ev.target.closest('.formtools-element');
+        const area = ev.target.closest('.formtools-content');
+        if ( source.closest('.formtools-content') ) {
+            if ( sibling ) {
+                area.insertBefore(source, sibling);
+            }
+            else {
+                area.insertBefore(source, area.children[area.children.length-1]);
+            }
+        }
+        else {
+            const source_copy = source.cloneNode(true);
+
+            const old_id = source_copy.id;
+            source_copy.id = 'formtools-element-' + area.dataset.pageId + '-' + Date.now();
+            jQuery(source_copy).find('#' + old_id + '-modal').attr('id', source_copy.id + '-modal' );
+            jQuery(source_copy).find('a.edit').attr('data-target', '#' + source_copy.id + '-modal' );
+            jQuery(source_copy).find('form.formtools-element-form').on('submit', formTools.elementSubmit);
+            jQuery(source_copy).find('.formtools-element-modal').modal('show');
+            jQuery(source_copy).attr('ondragenter', 'formTools.dragenter(event);');
+            if ( sibling ) {
+                area.insertBefore(source_copy, sibling);
+            }
+            else {
+                area.insertBefore(source_copy, area.children[area.children.length-1]);
+            }
+        }
+    },
+
+    dragend: function (ev) {
+        jQuery('.formtools-content:visible').find('.formtools-element-placeholder').removeClass('active hidden');
+        jQuery('.formtools-content:visible').find('.formtools-element').removeClass('current');
+        jQuery('.formtools-component-menu').find('.formtools-element').removeClass('current');
+    },
 
-    // Remove all of the items from the list.
-    dti.clear();
-}
+    elementSubmit: function(e) {
+        e.preventDefault();
+        const form = jQuery(this);
+        const element = form.closest('.formtools-element');
+        const value = element.data('value');
+
+        if ( value.type === 'raw_html' ) {
+            value.html = form.find(':input[name=html]').val();
+            element.find('span.content').text(value.html.length > 40 ? value.html.substr(0, 40) + '...' : value.html);
+        }
+        else if ( value.type === 'component' && value.comp_name === 'Field' ) {
+            const label = form.find(':input[name=label]').val();
+            if ( label.length ) {
+                value.arguments.label = label;
+            }
+            else {
+                delete value.arguments.label;
+            }
+
+            const default_value = form.find(':input[name=default]').val();
+
+            if ( default_value.length ) {
+                value.arguments.default = default_value;
+            }
+            else {
+                delete value.arguments.default;
+            }
+        }
+        else if ( value.type === 'hidden' ) {
+            value['input-name'] = form.find(':input[name=name]').val();
+            value['input-value'] = form.find(':input[name=value]').val();
+            element.find('span.content').text(value['input-name'] + ': ' + value['input-value']);
+        }
+        element.data('value', value);
+        form.closest('.formtools-element-modal').modal('hide');
+    },
+
+    submit: function(e) {
+        const form = jQuery(this);
+        const content = {};
+        jQuery('div.formtools-content').each(function() {
+            let page = jQuery(this).data('page');
+            content[page] ||= {};
+
+            for ( let attr of ['name', 'sort_order', 'validation'] ) {
+                if ( attr === 'validation' ) {
+                    content[page][attr] = jQuery(this).closest('div.tab-pane').find(':input[name="' + attr + '"]').is(':checked') ? 1 : 0;
+                }
+                else {
+                    content[page][attr] = jQuery(this).closest('div.tab-pane').find(':input[name="' + attr + '"]').val();
+                }
+            }
+
+            content[page]['content'] ||= [];
+            jQuery(this).children('.formtools-element').each(function() {
+                content[page]['content'].push(jQuery(this).data('value'));
+            });
+        });
+        form.find('input[name=ActiveTab]').val(jQuery('.formtools-content:visible').data('page-id'));
+        form.find('input[name=Content]').val(JSON.stringify(content));
+    },
+
+    deletePage: function() {
+        const tab = jQuery(this).closest('.tab-pane');
+
+        tab.fadeOut(function() {
+            jQuery('#formtools-pages').find('a.nav-link[href="#' + tab.attr('id') + '"]').closest('li').remove();
+            jQuery('#formtools-pages').find('li:first a.nav-link').tab('show');
+        }).remove();
+        return false;
+    }
+};

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

Summary of changes:
 META.yml                              |   1 +
 Makefile.PL                           |   1 +
 html/Admin/FormTools/Modify.html      | 515 +++++++++++++++++++++++++++++++---
 static/css/rt-extension-formtools.css |  41 +++
 static/js/rt-extension-formtools.js   | 208 +++++++++-----
 5 files changed, 664 insertions(+), 102 deletions(-)


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


More information about the Bps-public-commit mailing list