Commit 6b330cd4 by Mac Stephens

Add Google Sheets–style reportedBy/assignedTo dropdown name manager with…

Add Google Sheets–style reportedBy/assignedTo dropdown name manager with client-side offcanvas add flow and local option updates in TasksHTML
parent dbe3a026
File added
...@@ -8,6 +8,30 @@ ...@@ -8,6 +8,30 @@
</div> </div>
<div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div> <div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNameManager" aria-labelledby="nm_title">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="nm_title">Add Item</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div id="nm_existing_list" class="list-group mb-3"></div>
<div id="nm_add_wrap" class="d-none mb-3">
<input id="nm_name_input" type="text" class="form-control" maxlength="100">
<div id="nm_name_invalid" class="invalid-feedback d-none"></div>
<div class="d-flex justify-content-end mt-2">
<button id="btn_nm_save" type="button" class="btn btn-success">Save</button>
</div>
</div>
<button id="btn_nm_add_another" type="button" class="btn btn-secondary">
Add another item
</button>
</div>
</div>
</div> </div>
......
...@@ -5,7 +5,7 @@ interface ...@@ -5,7 +5,7 @@ interface
uses uses
System.SysUtils, System.Classes, System.SysUtils, System.Classes,
JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs, JS, Web, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
WEBLib.ExtCtrls, WEBLib.ExtCtrls, uNameManager,
XData.Web.Client, XData.Web.Dataset, XData.Web.Client, XData.Web.Dataset,
Utils, Data.DB, XData.Web.JsonDataset, Vcl.Controls, Vcl.StdCtrls, Utils, Data.DB, XData.Web.JsonDataset, Vcl.Controls, Vcl.StdCtrls,
WEBLib.StdCtrls; WEBLib.StdCtrls;
...@@ -36,6 +36,7 @@ type ...@@ -36,6 +36,7 @@ type
FTaskId: string; FTaskId: string;
FReportedByOptions: TJSArray; FReportedByOptions: TJSArray;
FAssignedToOptions: TJSArray; FAssignedToOptions: TJSArray;
FNameManager: TNameManager;
[async] procedure LoadTasks(const ATaskId: string); [async] procedure LoadTasks(const ATaskId: string);
procedure RenderTable; procedure RenderTable;
procedure BindTableEditors; procedure BindTableEditors;
...@@ -50,6 +51,10 @@ type ...@@ -50,6 +51,10 @@ type
procedure EditorBlur(Event: TJSEvent); procedure EditorBlur(Event: TJSEvent);
[async] function AddTaskRow: Boolean; [async] function AddTaskRow: Boolean;
function ExtractOptionNames(const SourceArray: TJSArray): TJSArray; function ExtractOptionNames(const SourceArray: TJSArray): TJSArray;
function GetOptionsForField(const AFieldName: string): TJSArray;
procedure FocusTrigger(const ATriggerId: string);
procedure DropdownItemClick(Event: TJSEvent);
procedure DropdownEditClick(Event: TJSEvent);
public public
end; end;
...@@ -71,6 +76,22 @@ begin ...@@ -71,6 +76,22 @@ begin
FReportedByOptions := TJSArray.new; FReportedByOptions := TJSArray.new;
FAssignedToOptions := TJSArray.new; FAssignedToOptions := TJSArray.new;
FNameManager := TNameManager.Create(
function(const AFieldName: string): TJSArray
begin
Result := GetOptionsForField(AFieldName);
end,
procedure
begin
RenderTable;
end,
procedure(const ATriggerId: string)
begin
FocusTrigger(ATriggerId);
end
);
FNameManager.BindControls;
if FTaskId = '' then if FTaskId = '' then
begin begin
...@@ -78,18 +99,19 @@ begin ...@@ -78,18 +99,19 @@ begin
Exit; Exit;
end; end;
if DMConnection.ApiConnection.Connected then btnAddRow.Enabled := False;
begin
LoadTasks(FTaskId);
Exit;
end;
if not DMConnection.ApiConnection.Connected then
begin
DMConnection.ApiConnection.Open( DMConnection.ApiConnection.Open(
procedure procedure
begin begin
LoadTasks(FTaskId); LoadTasks(FTaskId);
end end
); );
end
else
LoadTasks(FTaskId);
end; end;
...@@ -214,6 +236,20 @@ begin ...@@ -214,6 +236,20 @@ begin
el := TJSHTMLElement(nodes.item(i)); el := TJSHTMLElement(nodes.item(i));
el.addEventListener('change', TJSEventHandler(@SelectChange)); el.addEventListener('change', TJSEventHandler(@SelectChange));
end; end;
nodes := document.querySelectorAll('.task-dd-item');
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.addEventListener('click', TJSEventHandler(@DropdownItemClick));
end;
nodes := document.querySelectorAll('.task-dd-edit-btn');
for i := 0 to nodes.length - 1 do
begin
el := TJSHTMLElement(nodes.item(i));
el.addEventListener('click', TJSEventHandler(@DropdownEditClick));
end;
end; end;
...@@ -423,30 +459,49 @@ var ...@@ -423,30 +459,49 @@ var
var var
i: Integer; i: Integer;
itemText: string; itemText: string;
sel: string; triggerId: string;
begin begin
Result := triggerId := 'task_dd_' + FieldName + '_' + IntToStr(AIdx);
'<select class="form-select form-select-sm task-select" ' +
'data-idx="' + IntToStr(AIdx) + '" data-field="' + FieldName + '">';
sel := ''; Result :=
if Trim(Current) = '' then '<div class="dropdown w-100">' +
sel := ' selected'; '<button id="' + triggerId + '" class="btn btn-sm btn-light border w-100 d-flex justify-content-between align-items-center text-start task-dd-toggle" ' +
Result := Result + '<option value=""' + sel + '></option>'; 'type="button" data-bs-toggle="dropdown" aria-expanded="false">' +
'<span class="task-dd-label text-truncate">' + HtmlEncode(Current) + '</span>' +
'<span class="dropdown-toggle dropdown-toggle-split border-0 ms-2"></span>' +
'</button>' +
'<div class="dropdown-menu w-100 p-0 overflow-hidden">';
Result := Result +
'<button type="button" class="dropdown-item task-dd-item" ' +
'data-idx="' + IntToStr(AIdx) + '" ' +
'data-field="' + FieldName + '" ' +
'data-value=""></button>';
if Assigned(Items) then if Assigned(Items) then
begin
for i := 0 to Items.length - 1 do for i := 0 to Items.length - 1 do
begin begin
itemText := string(Items[i]); itemText := string(Items[i]);
sel := ''; Result := Result +
if SameText(Current, itemText) then '<button type="button" class="dropdown-item task-dd-item" ' +
sel := ' selected'; 'data-idx="' + IntToStr(AIdx) + '" ' +
Result := Result + '<option value="' + HtmlEncode(itemText) + '"' + sel + '>' + HtmlEncode(itemText) + '</option>'; 'data-field="' + FieldName + '" ' +
end; 'data-value="' + HtmlEncode(itemText) + '" ' +
end; 'data-trigger-id="' + triggerId + '">' + HtmlEncode(itemText) + '</button>';
end;
Result := Result + '</select>';
Result := Result +
'<div class="dropdown-divider my-1"></div>' +
'<div class="px-2 py-1 text-end">' +
'<button type="button" class="btn btn-link btn-sm p-0 text-body task-dd-edit-btn" ' +
'data-idx="' + IntToStr(AIdx) + '" ' +
'data-field="' + FieldName + '" ' +
'data-trigger-id="' + triggerId + '">' +
'<i class="fas fa-pencil-alt"></i>' +
'</button>' +
'</div>' +
'</div>' +
'</div>';
end; end;
function StatusSelect(const Current: string; const AIdx: Integer): string; function StatusSelect(const Current: string; const AIdx: Integer): string;
...@@ -676,6 +731,100 @@ begin ...@@ -676,6 +731,100 @@ begin
end; end;
function TFTasksHTML.GetOptionsForField(const AFieldName: string): TJSArray;
begin
if SameText(AFieldName, 'reportedBy') then
Result := FReportedByOptions
else if SameText(AFieldName, 'assignedTo') then
Result := FAssignedToOptions
else
Result := nil;
end;
procedure TFTasksHTML.FocusTrigger(const ATriggerId: string);
var
el: TJSHTMLElement;
begin
if ATriggerId = '' then
Exit;
el := TJSHTMLElement(document.getElementById(ATriggerId));
if Assigned(el) then
el.focus;
end;
procedure TFTasksHTML.DropdownItemClick(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr, fieldName, newVal, triggerId: string;
begin
if not xdwdsTasks.Active then
Exit;
Event.preventDefault;
el := TJSHTMLElement(Event.currentTarget);
idxStr := string(el.getAttribute('data-idx'));
fieldName := string(el.getAttribute('data-field'));
newVal := string(el.getAttribute('data-value'));
triggerId := string(el.getAttribute('data-trigger-id'));
idx := StrToIntDef(idxStr, -1);
if (idx < 0) or (fieldName = '') then
Exit;
GotoRowIndex(idx);
if xdwdsTasks.Eof then
Exit;
xdwdsTasks.Edit;
xdwdsTasks.FieldByName(fieldName).AsString := newVal;
xdwdsTasks.Post;
if triggerId <> '' then
begin
asm
var btn = document.getElementById(triggerId);
if (btn) {
var labelEl = btn.querySelector('.task-dd-label');
if (labelEl) {
labelEl.textContent = newVal;
}
}
end;
end;
SaveRow(idx);
end;
procedure TFTasksHTML.DropdownEditClick(Event: TJSEvent);
var
el: TJSHTMLElement;
idx: Integer;
idxStr, fieldName, triggerId: string;
begin
Event.preventDefault;
Event.stopPropagation;
el := TJSHTMLElement(Event.currentTarget);
idxStr := string(el.getAttribute('data-idx'));
fieldName := string(el.getAttribute('data-field'));
triggerId := string(el.getAttribute('data-trigger-id'));
idx := StrToIntDef(idxStr, -1);
if (idx < 0) or (fieldName = '') then
Exit;
FNameManager.OpenManager(fieldName, idx, triggerId);
end;
......
...@@ -11,7 +11,8 @@ uses ...@@ -11,7 +11,8 @@ uses
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html}, View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
Utils in 'Utils.pas', Utils in 'Utils.pas',
View.Test in 'View.Test.pas' {FTest: TWebForm} {*.html}, View.Test in 'View.Test.pas' {FTest: TWebForm} {*.html},
View.TasksHTML in 'View.TasksHTML.pas' {FTasksHTML: TWebForm} {*.html}; View.TasksHTML in 'View.TasksHTML.pas' {FTasksHTML: TWebForm} {*.html},
uNameManager in 'uNameManager.pas';
{$R *.res} {$R *.res}
......
...@@ -145,6 +145,7 @@ ...@@ -145,6 +145,7 @@
<FormType>dfm</FormType> <FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass> <DesignClass>TWebForm</DesignClass>
</DCCReference> </DCCReference>
<DCCReference Include="uNameManager.pas"/>
<None Include="index.html"/> <None Include="index.html"/>
<None Include="css\app.css"/> <None Include="css\app.css"/>
<None Include="config\config.json"/> <None Include="config\config.json"/>
......
<div class="container-fluid py-3">
<div class="card shadow-sm">
<div class="card-body">
<h4 class="mb-3">Home Form</h4>
<div class="mb-3">
<label for="edt_task_id" class="form-label">Task Id</label>
<input id="edt_task_id" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="edt_user_id" class="form-label">User Id</label>
<input id="edt_user_id" type="text" class="form-control">
</div>
<div class="mb-3">
<label for="edt_code" class="form-label">Code</label>
<input id="edt_code" type="text" class="form-control">
</div>
</div>
</div>
</div>
<nav class="navbar navbar-light bg-light login-navbar">
<div class="container-fluid">
<a class="navbar-brand" href="#">Koehler-Gibson Orders</a>
</div>
</nav>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-auto">
<img id="kgpicture" style="width: 250px; height: 250px;">
</div>
<div class="col-md-6 col-lg-4">
<div class="card login-card">
<div class="card-header">
<h3 id="view.login.title" class="fs-6 card-title">Please Sign In</h3>
</div>
<div class="card-body">
<div role="form">
<div id="view.login.message" class="alert alert-danger">
<button id="view.login.message.button" type="button" class="btn-close" aria-label="Close"></button>
<span id="view.login.message.label"></span>
</div>
<fieldset>
<div class="mb-3">
<input id="view.login.edtusername" class="form-control" type="text" autofocus placeholder="Username">
</div>
<div class="mb-3">
<input id="view.login.edtpassword" class="form-control" type="password" placeholder="Password">
</div>
<div class="mb-3">
<button id="view.login.btnlogin" class="btn btn-primary w-100">Login</button>
</div>
<div class="text-end text-muted small mt-1">
<span id="lbl_client_version"></span>
</div>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="wrapper" class="d-flex flex-column vh-100">
<nav class="navbar navbar-expand navbar-light bg-light" style="margin-bottom: 0px">
<div class="container-fluid">
<div class="d-flex align-items-center">
<a id="view.main.apptitle" class="navbar-brand" href="index.html">emT3web</a>
<span id="view.main.version" class="small text-muted ms-2"></span>
</div>
<li class="nav-item ms-2 me-2 d-flex align-items-center">
<input id="edt_task_id_main" type="text" class="form-control form-control-sm" placeholder="Task Id">
</li>
<div class="collapse navbar-collapse show" id="navbarNavDropdown">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-user fa-fw"></i><span class="panel-title" id="view.main.username">Username</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownMenuLink">
<li>
<a class="dropdown-item" id="dropdown.menu.logout" href="#">
<i class="fa fa-sign-out fa-fw"></i><span> Logout</span>
</a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<!-- Toast wrapper directly under navbar -->
<div id="toast-wrapper" class="position-fixed top-0 start-0 mt-5 ms-4" style="z-index: 1080; min-width: 300px; max-width: 500px">
<div id="bootstrapToast" class="toast align-items-center text-white bg-success border-0 shadow" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body" id="bootstrapToastBody">Success message</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
<div class="container-fluid d-flex flex-column flex-grow-1" style="min-height: 0">
<div id="main.webpanel" class="flex-grow-1 d-flex flex-column" style="min-height: 0"></div>
</div>
</div>
<div id="spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div class="modal fade" id="main_errormodal" tabindex="-1" aria-labelledby="main_lblmodal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="main_lblmodal">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="main_lblmodal_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary">
Restart
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="main_confirmation_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fw-bold" id="main_modal_body">Placeholder text</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary me-3" id="btn_confirm_left">
Cancel
</button>
<button type="button" class="btn btn-secondary" id="btn_confirm_right">
Confirm
</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="main_notification_modal" tabindex="-1" aria-labelledby="main_lblmodal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="main_notification_modal">Error</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="main_notification_modal_body">Please contact EMSystems to solve the issue.</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_close" class="btn btn-primary">
Close
</button>
</div>
</div>
</div>
</div>
<div class="container-fluid p-2 d-flex flex-column h-100">
<div class="d-flex align-items-center justify-content-between mb-2 flex-shrink-0">
<h5 class="mb-0" id="lbl_project_name"></h5>
<div class="d-flex gap-2">
<button id="btn_add_row" class="btn btn-sm btn-outline-success">Add Row</button>
<button id="btn_reload" class="btn btn-sm btn-outline-primary">Reload</button>
</div>
</div>
<div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div>
</div>
...@@ -6,7 +6,7 @@ LogFileNum=175 ...@@ -6,7 +6,7 @@ LogFileNum=175
[Database] [Database]
--Server=192.168.116.128 --Server=192.168.116.128
Server=192.168.102.129 Server=192.168.102.131
--Server=192.168.75.133 --Server=192.168.75.133
--Server=192.168.159.10 --Server=192.168.159.10
Database=eTask Database=eTask
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<noscript>Your browser does not support JavaScript!</noscript>
<link href="data:;base64,=" rel="icon"/>
<title>Em Systems - emT3 Web</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="css/app.css" rel="stylesheet" type="text/css"/>
<link href="css/spinner.css" rel="stylesheet" type="text/css"/>
<script crossorigin="anonymous" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" src="https://code.jquery.com/jquery-3.7.1.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="emT3web.js" type="text/javascript"></script>
</head>
<body>
</body>
<script type="text/javascript">rtl.run();</script>
</html>
...@@ -8,6 +8,30 @@ ...@@ -8,6 +8,30 @@
</div> </div>
<div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div> <div id="tasks_table_host" class="flex-grow-1 min-vh-0"></div>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNameManager" aria-labelledby="nm_title">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="nm_title">Add Item</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<div id="nm_existing_list" class="list-group mb-3"></div>
<div id="nm_add_wrap" class="d-none mb-3">
<input id="nm_name_input" type="text" class="form-control" maxlength="100">
<div id="nm_name_invalid" class="invalid-feedback d-none"></div>
<div class="d-flex justify-content-end mt-2">
<button id="btn_nm_save" type="button" class="btn btn-success">Save</button>
</div>
</div>
<button id="btn_nm_add_another" type="button" class="btn btn-secondary">
Add another item
</button>
</div>
</div>
</div> </div>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment