[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