using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Comal.Classes;
using InABox.Clients;
using InABox.Core;
using PRSClasses;
using PRSServer.Properties;
using RazorEngine.Templating;
using RazorEngine.Text;
using Syncfusion.Pdf.Parsing;
using Color = System.Windows.Media.Color;
using ColorConverter = System.Windows.Media.ColorConverter;
using Encoder = System.Drawing.Imaging.Encoder;
namespace PRSServer
{
///
/// Provides utility functions for writing Razor template pages
///
public class WebUtils
{
#region Entity Editor
///
/// Builds an editor for a given entity.
///
/// To get reasonable formatting, import in a <style> tag
/// The type of
/// The entity which contains the data to be pre-inserted into the editor
/// An optional HTML id to add to root <div> of the generated item.
/// An optional list of HTML classes to add to root <div> of the generated item.
/// A string containing raw HTML markup
public static string BuildEntityEditor(T entity, string? id = null, string[]? classList = null) where T : Entity
{
var builder = new StringBuilder();
builder.AppendFormat(
@"
",
classList != null ? " " + string.Join(" ", classList) : "",
id != null ? " id=\"" + id + "\"" : ""
);
var layout = DFLayout.GenerateEntityLayout();
var data = WebHandler.SerializeEntityForEditing(typeof(T), entity);
var markup = BuildDigitalFormViewer(layout, data, false, submitLabel: "Save", saveLabel: null);
builder.Append(markup).Append("
");
return builder.ToString();
}
#endregion
#region CoreTable Viewer
///
/// Used for determining which columns to show in an HTML representation of a CoreTable.
/// EntityTableColumns.ALL means to show every column
/// EntityTableColumns.VISIBLE means only show columns which do not have their visiblity set to hidden
/// EntityTableColumns.REQUIRED means only show columns which have their visibility set to visible.
/// EntityTableColumns.LOOK_UP means to use the Lookup associated with the entity to generate the columns.
///
public enum EntityTableColumns
{
ALL,
VISIBLE,
LOOKUP,
REQUIRED
}
private static List GetColumnsForTable(EntityTableColumns showType = EntityTableColumns.ALL) where T : Entity
{
if (showType == EntityTableColumns.LOOKUP) return LookupFactory.DefineColumns(typeof(T)).ColumnNames().ToList();
var columns = new List();
var properties = CoreUtils.PropertyInfoList(
typeof(T),
x => x.GetCustomAttribute() == null &&
x.GetCustomAttribute() == null &&
x.PropertyType != typeof(UserProperties), true);
switch (showType)
{
case EntityTableColumns.VISIBLE:
foreach (var property in properties)
{
var editor = property.Value.GetEditor();
if (editor != null && editor.Visible != Visible.Hidden) columns.Add(property.Key);
}
break;
case EntityTableColumns.REQUIRED:
foreach (var property in properties)
{
var editor = property.Value.GetEditor();
if (editor != null && editor.Visible == Visible.Default) columns.Add(property.Key);
}
break;
case EntityTableColumns.ALL:
foreach (var property in properties.Keys) columns.Add(property);
break;
}
return columns;
}
///
/// Returns a string containing an HTML table representing the data present in a CoreTable.
///
/// The type of the entity that the table represents.
/// The table to be converted to HTML
/// A flag to determine which columns to show.
/// The HTML string
public static string BuildHTMLTableFromCoreTable(CoreTable table, EntityTableColumns showType = EntityTableColumns.ALL) where T : Entity
{
var columns = GetColumnsForTable(showType);
var html = new List();
html.Add("
");
html.Add(typeof(T).Name);
html.Add("
");
foreach (var column in columns)
{
html.Add("
");
html.Add(column);
html.Add("
");
}
html.Add("
");
foreach (var row in table.Rows)
{
html.Add("
");
foreach (var column in columns)
{
html.Add("
");
html.Add(row.Get
");
}
html.Add("
");
}
html.Add("
");
html.Add("");
return string.Concat(html);
}
///
/// Returns a string containing an HTML table representing the data present in a CoreTable.
///
/// The type of the entity that the table represents
/// The table to be converted to HTML
/// A flag to determine which columns to show.
/// The HTML string
public static string BuildHTMLTableFromCoreTable(Type entityType, CoreTable table, EntityTableColumns showType = EntityTableColumns.ALL)
{
var fnc = typeof(WebUtils).GetMethod(nameof(BuildHTMLTableFromCoreTable), 1, new[] { typeof(CoreTable), typeof(EntityTableColumns) })!
.MakeGenericMethod(entityType);
return (fnc.Invoke(null, new object[] { table, showType }) as string)!;
}
#endregion
#region Extra Utilities
public static string ConvertStringToPlainHTML(string text)
{
return text.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """).ReplaceLineEndings(" ");
}
public enum ImageEncoding
{
JPEG
}
private static ImageCodecInfo? GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
public static List RenderPDFToImages(byte[] pdfData, ImageEncoding encoding = ImageEncoding.JPEG)
{
var rendered = new List();
PdfLoadedDocument loadeddoc = new PdfLoadedDocument(pdfData);
Bitmap[] images = loadeddoc.ExportAsImage(0, loadeddoc.Pages.Count - 1);
var jpgEncoder = GetEncoder(ImageFormat.Jpeg)!;
var quality = Encoder.Quality;
var encodeParams = new EncoderParameters(1);
encodeParams.Param[0] = new EncoderParameter(quality, 100L);
if (images != null)
foreach (var image in images)
{
using(var data = new MemoryStream())
{
image.Save(data, jpgEncoder, encodeParams);
rendered.Add(data.ToArray());
}
}
return rendered;
}
private static readonly ImageConverter converter = new();
public static string WebDocumentToDataURL(string code)
{
var document = new Client().Query(
new Filter(x => x.ID)
.InQuery(new SubQuery(
new Filter(x => x.Code).IsEqualTo(code),
new Column(x => x.Document.ID))),
new Columns(x => x.Data));
var data = (byte[]?)document.Rows.FirstOrDefault()?["Data"];
return "data:;base64," + (data != null ? Convert.ToBase64String(data) : "");
}
public static Guid? WebDocumentID(string code)
{
var document = new Client().Query(
new Filter(x => x.ID)
.InQuery(new SubQuery(
new Filter(x => x.Code).IsEqualTo(code),
new Column(x => x.Document.ID))),
new Columns(x => x.Data));
return (Guid?)document.Rows.FirstOrDefault()?["ID"];
}
public static string ResourceToDataURL(Bitmap resource)
{
return "data:;base64," + Convert.ToBase64String((byte[])converter.ConvertTo(resource, typeof(byte[]))!);
}
#endregion
#region Security
public static bool UserCanAccess(IDataModel model) where T : ISecurityDescriptor, new()
{
var userTable = model.GetTable();
if (userTable.Rows.Count == 0) return false;
var user = userTable.Rows[0].ToObject();
return Security.IsAllowed(user.ID, user.SecurityGroup.ID);
}
public static bool IsLoggedIn(IDataModel model)
{
var userTable = model.GetTable();
return userTable.Rows.Count > 0;
}
public static User? GetUser(IDataModel model)
{
var userTable = model.GetTable();
if (userTable.Rows.Count == 0) return null;
return userTable.Rows[0].ToObject();
}
#endregion
#region Directory Viewer
public static string DirectoryViewerStyle { get; } = @"";
private interface IFileItem
{
public string FilePath { get; }
public string FileName { get; }
}
private class FileItem : IFileItem
{
public FileItem(string filePath, string fileName)
{
FilePath = filePath;
FileName = fileName;
}
public string FilePath { get; }
public string FileName { get; }
}
private class DirectoryItem : IFileItem
{
public readonly List Directories = new();
public readonly List Files = new();
public DirectoryItem(string filePath, string fileName)
{
FilePath = filePath;
FileName = fileName;
}
public string FilePath { get; }
public string FileName { get; }
}
private static DirectoryItem? GetDirectoryStructure(string path, Func? fileFilter, Func? directoryFilter,
string? name = null, int depth = 2)
{
var fileName = name ?? path;
if (depth == 0) return new DirectoryItem(path, fileName);
if (!Directory.Exists(path)) return null;
var directory = new DirectoryItem(path, fileName);
foreach (var item in Directory.GetDirectories(path))
if (directoryFilter?.Invoke(item) != false)
try
{
var subDirectory = GetDirectoryStructure(item, fileFilter, directoryFilter, Path.GetFileName(item), depth - 1);
if (subDirectory != null) directory.Directories.Add(subDirectory);
}
catch (UnauthorizedAccessException)
{
}
foreach (var item in Directory.GetFiles(path))
if (fileFilter?.Invoke(item) != false)
directory.Files.Add(new FileItem(item, Path.GetFileName(item)));
return directory;
}
private static DirectoryItem? GetDirectoryStructure(string path, string? fileFilter, string? directoryFilter, string? name = null,
int depth = 2)
{
var fileFilterRgx = fileFilter != null ? new Regex(fileFilter) : null;
var dirFilterRgx = directoryFilter != null ? new Regex(directoryFilter) : null;
return GetDirectoryStructure(
path,
fileFilterRgx != null ? x => fileFilterRgx.IsMatch(x) : null,
dirFilterRgx != null ? x => dirFilterRgx.IsMatch(x) : null,
name, depth
);
}
private static readonly string DirectoryViewerScript = @"";
private static void BuildDirectory(StringBuilder builder, DirectoryItem directory)
{
builder.Append(@"
");
}
}
builder.Append(@"");
}
///
///
/// The path to the folder in which to initialise the directory viewer
/// A file filter which is compiled to a Regex. Regex.IsMatch is used to filter files
///
/// A directory filter which is compiled to a Regex. Regex.IsMatch is used to filter
/// directories
///
/// The name to call the root folder. If null, defaults to the directory path.
/// An HTML id attribute for the viewer
/// An HTML class list for the viewer
/// The depth of directories to search
///
public static string BuildDirectoryViewer(string path, Func fileFilter, Func? directoryFilter = null,
string? alias = null, string? id = null, string[]? classList = null, int depth = 2)
{
var stringBuilder = new StringBuilder();
stringBuilder.Append(@"
");
var structure = GetDirectoryStructure(path, fileFilter, directoryFilter, alias ?? path, depth);
if (structure != null) BuildDirectory(stringBuilder, structure);
stringBuilder.Append(@"
");
stringBuilder.Append(DirectoryViewerScript);
return stringBuilder.ToString();
}
public static string BuildDirectoryViewer(string path, string fileFilter, string? directoryFilter = null, string? alias = null,
string? id = null,
string[]? classList = null, int depth = 2)
{
var fileFilterRgx = new Regex(fileFilter);
var dirFilterRgx = directoryFilter != null ? new Regex(directoryFilter) : null;
return BuildDirectoryViewer(
path,
x => fileFilterRgx.IsMatch(x),
dirFilterRgx != null ? x => dirFilterRgx.IsMatch(x) : null,
alias, id, classList, depth
);
}
#endregion
#region JSON Entities
public static Dictionary SerializeEntity(Type entityType, Entity entity)
{
var data = new Dictionary();
foreach (var property in DatabaseSchema.Properties(entityType))
{
data[property.Name] = CoreUtils.GetPropertyValue(entity, property.Name);
}
return data;
}
///
/// Generates a JSON object for javascript
///
///
///
///
public static string EntityToJSON(T entity) where T : Entity
{
var serializedEntity = SerializeEntity(typeof(T), entity);
var serializedJSON = Serialization.Serialize(serializedEntity);
return serializedJSON;
}
public static string CreateJSONEntity() where T : Entity, new()
{
var serializedEntity = SerializeEntity(typeof(T), new T());
var serializedJSON = Serialization.Serialize(serializedEntity);
return serializedJSON;
}
///
/// Generate code for setting a property of a JSON entity
///
///
/// A Javascript expression that evaluates to the entity JSON
///
///
public static string JSONSetProperty(string entityExpr, Expression> expression, string value) where T : Entity
{
var propName = CoreUtils.GetFullPropertyName(expression, ".");
return $"({entityExpr})[\"{propName}\"]={value}";
}
///
/// Generate code for getting a property of a JSON entity
///
///
/// A Javascript expression that evaluates to the entity JSON
///
///
public static string JSONGetProperty(string entityExpr, Expression> expression) where T : Entity
{
var propName = CoreUtils.GetFullPropertyName(expression, ".");
return $"({entityExpr})[\"{propName}\"]";
}
#endregion
#region Digital Form Viewer
private const string _multiImageStyle = @"
.prs-web_utils-digital_form-multi_image {
}
.prs-web_utils-digital_form-multi_image_cont {
overflow: auto;
position: relative;
height: 10em;
background: #999;
box-shadow: black inset 0 0 4px;
}
.prs-web_utils-digital_form-multi_image_cont > div {
position: absolute;
top: 0;
bottom: 0;
display: flex;
gap: 1em;
padding: 0.4em;
}
.prs-web_utils-digital_form-multi_image_cont img {
max-height: 100%;
}
.prs-web_utils-digital_form-multi_image_cont img.selected {
border: dashed 2px black;
}
.prs-web_utils-digital_form-multi_image_btns {
display: flex;
gap: 0.2em;
}
.prs-web_utils-digital_form-multi_image_btns .prs-button {
flex: 1;
height: 4em;
display: flex;
justify-content: center;
align-items: center;
}
";
private static string _digitalFormViewerStyle = @"
.prs-web_utils-digital_form {
display: grid;
column-gap: 0.4em;
row-gap: 0.4em;
}
.prs-web_utils-digital_form-field {
min-height: 2em;
}
@media (pointer:coarse), (pointer:none) {
.prs-web_utils-digital_form-field {
min-height: 4em;
}
}
.prs-web_utils-digital_form * {
box-sizing: border-box;
}
.prs-web_utils-digital_form-label {
text-align: left;
padding: 1.5em 0.3em;
align-self: center;
overflow-wrap: anywhere;
}
.prs-web_utils-digital_form-image {
max-width: 100%;
max-height: 100%;
}
.prs-web_utils-digital_form-color,
.prs-web_utils-digital_form-integer,
.prs-web_utils-digital_form-text,
.prs-web_utils-digital_form-string,
.prs-web_utils-digital_form-url,
.prs-web_utils-digital_form-password,
.prs-web_utils-digital_form-date,
.prs-web_utils-digital_form-datetime {
background-color: white;
}
.prs-web_utils-digital_form-time {
display: flex;
align-items: center;
width: min-content;
background: white;
border: solid 1px gray;
}
.prs-web_utils-digital_form-time_hours {
text-align: right;
}
.prs-web_utils-digital_form-time_mins {
text-align: left;
}
.prs-web_utils-digital_form-time span {
margin: 0.1em;
}
.prs-web_utils-digital_form-time input {
height: 100%;
width: 3em;
font-family: monospace;
border: none;
background: none;
}
.prs-web_utils-digital_form-time input:active {
background: #ddd;
}
.prs-web_utils-digital_form-timestamp {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.4em;
}
.prs-web_utils-digital_form-timestamp input {
flex: 1;
height: 100%;
}
.prs-web_utils-digital_form-timestamp .prs-button {
height: 100%;
padding: 1em;
display: flex;
align-items: center;
}
.prs-web_utils-digital_form-pin {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.4em;
}
.prs-web_utils-digital_form-pin input {
flex: 1;
height: 100%;
}
.prs-web_utils-digital_form-pin .prs-button {
height: 100%;
padding: 1em;
display: flex;
align-items: center;
}
.prs-web_utils-digital_form-document {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.4em;
}
.prs-web_utils-digital_form-document input {
flex: 1;
height: 100%;
}
.prs-web_utils-digital_form-document .prs-button {
height: 100%;
padding: 1em;
display: flex;
align-items: center;
}
.prs-web_utils-digital_form-notes {
display: flex;
gap: 0.2em;
}
.prs-web_utils-digital_form-note_cont {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.2em;
}
.prs-web_utils-digital_form-note {
resize: none;
width: 100%;
min-height: 6em;
background-color: #cdcdcd;
}
.prs-web_utils-digital_form-note.active {
background-color: lightgoldenrodyellow;
}
.prs-web_utils-digital_form-note_error {
color: black;
text-align: left;
}
.prs-web_utils-digital_form-note_add {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
background-color: #dedede;
border: solid 1px black;
border-radius: 3px;
user-select: none;
width: 2em;
}
@media (hover:hover) {
.prs-web_utils-digital_form-note_add:hover {
cursor: pointer;
background-color: #cacaca;
}
}
.prs-web_utils-digital_form-note_add:active {
background-color: #ccc;
}
.prs-web_utils-digital_form-embedded_image, .prs-web_utils-digital_form-signature {
max-width: 100%;
border-radius: 3px;
min-height: 2em;
display: flex;
flex-direction: column;
}
.prs-web_utils-digital_form-img_cont {
flex: 1;
overflow: hidden;
}
.prs-web_utils-digital_form-clear_img {
height: 4em;
display: flex;
justify-content: center;
align-items: center;
}
.prs-web_utils-digital_form-img {
max-width: 100%;
max-height: 100%;
}
div.prs-web_utils-digital_form-boolean {
display: flex;
align-items: center;
gap: 0.2em;
}
.prs-web_utils-digital_form-boolean label {
flex: 1;
text-align: left;
}
input.prs-web_utils-digital_form-boolean {
width: min-content;
height: min-content;
margin: auto;
}
.prs-web_utils-digital_form-lookup, .prs-web_utils-digital_form-option {
min-width: 0;
min-height: 2em;
background-color: white;
}
.prs-web_utils-digital_form-option_fake {
visibility:hidden;
margin-left:1em;
overflow:hidden;
min-height: 2em;
}
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button {
display: inline-block;
flex: 1;
height: 100%;
position: relative;
height: 2em;
background: none;
border: none;
}
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input {
appearance: none;
background-color: #a7a7a7;
border-radius: 3px;
border: dashed 1px black;
margin: 0;
width: 100%;
height: 100%;
}
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:checked {
background-color: #e7e7e7;
border: solid 1px black;
}
@media (hover:hover) {
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:hover {
cursor:pointer;
background-color: #a3a3a3;
}
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:checked:hover {
background-color: #e0e0e0;
}
}
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:active {
background-color: #979797;
}
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button input:checked:active {
background-color: #c6c6c6;
}
.prs-web_utils-digital_form-boolean .prs-web_utils-digital_form-button span {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
pointer-events: none;
line-height: 2em;
}
.prs-web_utils-digital_form-radio_cont {
display: flex;
justify-content: stretch;
align-items: center;
}
.prs-web_utils-digital_form-radio_cont label {
flex: 1;
text-align: left;
}
.prs-web_utils-digital_form-save_cont {
margin-top: 2em;
display: flex;
gap: 0.2em;
}
.prs-web_utils-digital_form-save_cont .prs-button {
height: 4em;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.prs-web_utils-digital_form-add_task {
display: flex;
gap: 0.4em;
}
.prs-web_utils-digital_form-add_task .prs-button {
height: 4em;
padding: 0 1em;
display: flex;
align-items: center;
justify-content: center;
}
.prs-web_utils-digital_form-add_task input {
flex: 1;
}
.prs-web_utils-digital_form input:invalid {
background-color: #FFDDDD;
}
.prs-web_utils-digital_form-field:required {
border: solid 1px #edaf58;
}
.prs-web_utils-digital_form-validate {
position: absolute;
top: 1em;
left: 1em;
height: 3em;
line-height: 3em;
padding: 0 1em;
background: white;
border: black solid 1px;
border-radius: 3px;
box-shadow: grey 0 0 10px;
transition: opacity 1s linear 0s;
}
" + _multiImageStyle;
///
/// A string which contains a stylesheet used for formatting digital forms. This is not necessary, but can be useful as
/// a base.
/// Include within the <style> tag in your document head.
///
public static string DigitalFormViewerStyle => _digitalFormViewerStyle;
private const string _submitScript = @"
var submitButtons = document.getElementsByClassName(""prs-web_utils-digital_form-submit"");
var saveButtons = document.getElementsByClassName(""prs-web_utils-digital_form-save"");
function getData(element){
if(element.classList.contains(""prs-web_utils-digital_form-double"")
|| element.classList.contains(""prs-web_utils-digital_form-integer"")){
var res = element.valueAsNumber;
if(Number.isNaN(res)){
return null;
}
return res;
} else if(element.classList.contains(""prs-web_utils-digital_form-color"")){
return ""#FF"" + element.value.substring(1).toUpperCase();
} else if(element.classList.contains(""prs-web_utils-digital_form-multi_image"")){
let imgCont = element.getElementsByClassName(""prs-web_utils-digital_form-multi_image_cont"")[0];
let imgContDiv = imgCont.children[0];
let imgs = [];
for(let img of imgContDiv.children){
imgs.push(img.src.replace(/^data:(.*,)?/, ''));
}
return imgs;
} else if(element.classList.contains(""prs-web_utils-digital_form-notes"")){
let cont = element.getElementsByClassName(""prs-web_utils-digital_form-note_cont"")[0];
let retVal = [];
for(let note of cont.children){
retVal.push(note.value);
}
if(!(/\S/.test(retVal[retVal.length - 1]))){
retVal.pop();
}
return retVal;
} else if(element.classList.contains(""prs-web_utils-digital_form-time"")){
let hours = element.getElementsByClassName(""prs-web_utils-digital_form-time_hours"")[0];
let mins = element.getElementsByClassName(""prs-web_utils-digital_form-time_mins"")[0];
return hours.value + "":"" + mins.value;
} else if(element.classList.contains(""prs-web_utils-digital_form-date"")
|| element.classList.contains(""prs-web_utils-digital_form-datetime"")){
return element.value;
} else if(element.classList.contains(""prs-web_utils-digital_form-pin"")){
let pinText = element.getElementsByClassName(""prs-web_utils-digital_form-pin_text"")[0];
return pinText.value;
} else if(element.classList.contains(""prs-web_utils-digital_form-timestamp"")){
let dateText = element.getElementsByClassName(""prs-web_utils-digital_form-timestamp_date"")[0];
return dateText.value;
} else if(element.classList.contains(""prs-web_utils-digital_form-text"")
|| element.classList.contains(""prs-web_utils-digital_form-string"")
|| element.classList.contains(""prs-web_utils-digital_form-password"")
|| element.classList.contains(""prs-web_utils-digital_form-url"")
|| element.classList.contains(""prs-web_utils-digital_form-code"")
|| element.classList.contains(""prs-web_utils-digital_form-option"")){
return element.value;
} else if(element.classList.contains(""prs-web_utils-digital_form-radio_cont"")){
var options = element.getElementsByTagName(""input"");
for(let option of options){
if(option.checked){
return option.value;
}
}
return null;
} else if(element.classList.contains(""prs-web_utils-digital_form-lookup"")){
return element.value;
} else if(element.classList.contains(""prs-web_utils-digital_form-embedded_image"")
|| element.classList.contains(""prs-web_utils-digital_form-signature"")){
var imgs = element.getElementsByClassName(""prs-web_utils-digital_form-img"");
if(imgs.length == 0){
return null;
} else {
var img = imgs[0];
var src = img.getAttribute(""src"");
var match = /data:[^;]*;base64,/d.exec(src);
if(match){
return src.substring(match.indices[0][1]);
}
return null;
}
} else if(element.classList.contains(""prs-web_utils-digital_form-document"")){
return element.getAttribute(""data-id"");
} else if(element.classList.contains(""prs-web_utils-digital_form-boolean"")){
if(element.tagName == ""INPUT""){
return element.checked;
} else if(element.tagName == ""SELECT""){
if(element.selectedOptions.length > 0){
return element.selectedOptions[0].hasAttribute(""data-true"");
}
return null;
} else if(element.tagName == ""DIV""){
for(var element of element.getElementsByTagName(""input"")){
if(element.checked){
return element.hasAttribute(""data-true"");
}
}
return null;
}
} else if(element.classList.contains(""prs-web_utils-digital_form-add_task"")){
var input = element.getElementsByTagName(""input"")[0];
return input.value;
}
}
function validate(element){
let valid = true;
let err = """";
if(element.classList.contains(""prs-web_utils-digital_form-embedded_image"")
|| element.classList.contains(""prs-web_utils-digital_form-signature"")){
let imgCont = element.getElementsByClassName(""prs-web_utils-digital_form-img_cont"")[0];
let imgs = imgCont.getElementsByTagName(""img"");
if(imgs.length == 0){
valid = false;
err = ""Please provide an image"";
}
} else if(element.classList.contains(""prs-web_utils-digital_form-multi_image"")){
let imgCont = element.getElementsByClassName(""prs-web_utils-digital_form-multi_image_cont"")[0];
let imgContDiv = imgCont.children[0];
if(imgContDiv.children.length == 0){
valid = false;
err = ""Please provide at least one image"";
}
}
return [valid, err];
}
function doValidationCheck(form){
var validations = form.getElementsByClassName(""prs-web_utils-digital_form-validate"");
for(let validation of validations){
validation.remove();
}
if(!form.reportValidity()){ return false; }
let valid = true;
var elements = form.getElementsByClassName(""prs-web_utils-digital_form-field"");
for(var element of elements){
if(element.hasAttribute(""required"")){
var [elValid, err] = validate(element);
valid = elValid && valid;
if(!elValid){
element.scrollIntoView();
let validation = document.createElement(""div"");
validation.classList.add(""prs-web_utils-digital_form-validate"");
validation.textContent = err;
element.style.position = ""relative"";
element.appendChild(validation);
setTimeout(() => {validation.style.opacity = 0;}, 5000);
}
}
}
return valid;
}
function submitForm(form, save, validate = true){
if(!validate || doValidationCheck(form)){
var formData = {};
var elements = form.getElementsByClassName(""prs-web_utils-digital_form-field"");
for(var element of elements){
formData[element.getAttribute(""name"")] = getData(element);
if(element.classList.contains(""prs-web_utils-digital_form-lookup"")){
for(var option of element.getElementsByTagName(""option"")){
if(option.value == element.value){
formData[element.getAttribute(""name"") + ""$ID""] = option.getAttribute(""data-id"");
break;
}
}
}
}
let eventName = save ? ""prs-web_utils-df_save"" : ""prs-web_utils-df_submit"";
const submitFormEvent = new CustomEvent(eventName, { detail: { formData: formData } });
form.dispatchEvent(submitFormEvent);
}
}
for(var btn of submitButtons){
btn.addEventListener(""click"", function(e) {
var form = e.target.parentNode.parentNode;
submitForm(form, false, true);
});
}
for(var btn of saveButtons){
btn.addEventListener(""click"", function(e) {
var form = e.target.parentNode.parentNode;
submitForm(form, true, false);
});
}
";
private static string GetDigitalFormViewerScript()
{
return @"";
}
public static string DigitalFormViewerScript => GetDigitalFormViewerScript();
private static string BuildLookupField(
IEnumerable> options,
string? selectedString,
string style,
string className,
bool readOnly,
bool required,
string name
)
{
var element = new StringBuilder(
string.Format(@"
", style));
var childOptions = new StringBuilder();
var display = selectedString ?? "";
foreach (var option in options)
{
var idString = option.Item2 != null ? $" data-id=\"{option.Item2}\"" : "";
childOptions.Append($"");
}
element.AppendFormat(
@"
{0}
");
return element.ToString();
}
private static string BuildRadioButtons(
IEnumerable options,
string? selectedString,
string style,
string className,
bool readOnly,
bool required,
string name
)
{
var element = new StringBuilder(
string.Format(@"
", name, className, style));
var childOptions = new StringBuilder();
var radioOptions = string.Format(
@"name=""{0}"" type=""radio""{1}{2}",
name,
required ? " required" : "",
readOnly ? " disabled" : ""
);
foreach (var option in options)
{
var selected = option == selectedString;
var valueStr = $" value=\"{option}\"";
var optionID = Guid.NewGuid();
childOptions.Append($""
+ $"");
}
element.Append(childOptions).Append("
");
return element.ToString();
}
public static string BuildDigitalFormViewer(
DFLayout layout,
Dictionary? data,
bool readOnly,
string? id = null, string[]? classList = null,
string submitLabel = "Submit Form", string? saveLabel = "Save Form"
)
{
StringBuilder stringBuilder = new();
stringBuilder.Append("");
stringBuilder.Append(DigitalFormViewerScript);
return stringBuilder.ToString();
}
///
/// Builds a layout for viewing and optionally editing digital forms.
///
///
///
/// In addition to outputting an HTML representation of the form, a script tag is outputted, which provides form
/// submission functionality and some extra layouting functionality.
/// The submit form button has the class of "prs-web_utils-digital_form-submit", and when clicked calls an event
/// named "prs-web_utils-df_submit" on the root form tag itself.
/// This event is a JavaScript CustomEvent object with detail.formData set to a JSON object containing data which
/// can be sent directly to www.domain.com/form_submission/XXXXForm?id=ID, allowing for the form to be submitted.
///
///
/// The instance of the form
/// The entity associated with the form
/// If set to true, causes all elements to be disabled and the Submit Form button to be omitted
/// An optional HTML id to add to root <div> of the generated item.
/// An optional list of HTML classes to add to root <div> of the generated item.
/// A string containing raw HTML markup
public static string BuildDigitalFormViewer(IBaseDigitalFormInstance form, Entity entity, bool readOnly, string? id = null,
string[]? classList = null)
{
var formLayoutTable = new Client().Query(
new Filter(x => x.Form.ID).IsEqualTo(form.Form.ID),
new Columns(x => x.Type).Add(x => x.Layout)
);
var variables = new Client().Load(new Filter(x => x.Form.ID).IsEqualTo(form.Form.ID));
var digitalFormLayout =
(formLayoutTable.Rows.FirstOrDefault(x => (DFLayoutType)x["Type"] == DFLayoutType.Mobile)
?? formLayoutTable.Rows.FirstOrDefault(x => (DFLayoutType)x["Type"] == DFLayoutType.Desktop))?.ToObject();
var layout = digitalFormLayout != null
? DFLayout.FromLayoutString(digitalFormLayout.Layout)
: DFLayout.GenerateAutoMobileLayout(variables);
layout.LoadVariables(variables);
var data = DigitalForm.ParseFormData(form.FormData, variables, entity);
return BuildDigitalFormViewer(layout, data, readOnly, id, classList);
}
#endregion
}
public abstract class WebTemplateBase : TemplateBase
{
public IEncodedString JSON() where U : Entity, new()
{
return new RawString(WebUtils.CreateJSONEntity());
}
public IEncodedString JSON(U entity) where U : Entity
{
return new RawString(WebUtils.EntityToJSON(entity));
}
public IEncodedString Get(string entity, Expression> expression) where U : Entity
{
return new RawString(WebUtils.JSONGetProperty(entity, expression));
}
public IEncodedString Set(string entity, Expression> expression, string value) where U : Entity
{
return new RawString(WebUtils.JSONSetProperty(entity, expression, value));
}
}
}