Skip to content

Commit

Permalink
Contextual help added to view creation (#416)
Browse files Browse the repository at this point in the history
  • Loading branch information
droberts-ctrlo authored Jul 8, 2024
1 parent f15f04c commit 227c70e
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 16 deletions.
9 changes: 9 additions & 0 deletions src/frontend/components/button/_button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,15 @@ $btn-border-radius: 23px;
}
}

.btn-help {
&::before {
@extend %icon-font;
transition: all 0.2s ease;

content: "\E816";
}
}

.btn-plain {
border: 0;
color: $gray-extra-dark;
Expand Down
4 changes: 2 additions & 2 deletions src/frontend/components/button/lib/save-view-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ export default function createSaveViewButtonComponent(el: JQuery<HTMLElement>) {
const $input = $form.find('input[type=hidden][name=group_id]');
if ((ev.target as HTMLInputElement)?.checked) {
$input.attr('required', 'required');
if ($dropdown.attr("placeholder").match(/All [Uu]sers/)) $dropdown.addClass('select--required');
if ($dropdown && $dropdown.attr && $dropdown.attr("placeholder") && $dropdown.attr("placeholder").match(/All [Uu]sers/)) $dropdown.addClass('select--required');
} else {
$input.removeAttr('required');
if ($dropdown.attr("placeholder").match(/All [Uu]sers/)) $dropdown.removeClass('select--required');
if ($dropdown && $dropdown.attr && $dropdown.attr("placeholder") && $dropdown.attr("placeholder").match(/All [Uu]sers/)) $dropdown.removeClass('select--required');
}
});
el.on('click', (ev) => {
Expand Down
19 changes: 19 additions & 0 deletions src/frontend/components/help-view/_help-view.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.help-view
{
a::after {
@extend %icon-font;
display: block;
font-size: 1.125rem;
color: $brand-secundary;
max-width: 1.125rem;
content: "\E816";
}

label {
font-weight: bold;

&.checkbox-label {
font-weight: normal;
}
}
}
6 changes: 6 additions & 0 deletions src/frontend/components/help-view/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { initializeComponent } from "component";
import HelpView from "./lib/component";

export default (scope) =>{
initializeComponent(scope, ".help-view", HelpView);
};
54 changes: 54 additions & 0 deletions src/frontend/components/help-view/lib/component.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import HelpView from "./component";

declare global {
interface Window {
$: JQueryStatic;
alert: (message?: any)=>void;
}
}

window.$ = require("jquery");

export {};

class TestHelpView extends HelpView {
public get button() {
return this.$button;
}
}

describe("help view tests", ()=> {
it("throws an error when help-text is not provided", ()=> {
const element = document.createElement("div");
expect(()=>new HelpView(element)).toThrow("help-text is required");
});

it("throws when help-target is not provided", () =>{
const element = document.createElement("div");
element.setAttribute("data-help-text", "help text");
expect(()=>new HelpView(element)).toThrow("help-target is required");
});

it("throws when help-target is not found", ()=>{
const element = document.createElement("div");
element.setAttribute("data-help-text", "help text");
const helpTarget = "help-target";
element.setAttribute("data-help-target", helpTarget);
expect(()=>new HelpView(element)).toThrow(`Could not find help target with id: ${helpTarget}`);
});

it("links the help text to the help target", ()=>{
const element = document.createElement("div");
element.setAttribute("data-help-text", "help text");
const helpTarget = "help-target";
element.setAttribute("data-help-target", helpTarget);
document.body.appendChild(element);
const target = document.createElement("div");
target.id = helpTarget;
document.body.appendChild(target);
const v = new TestHelpView(element);
expect(v.button?.length).toBe(1);
v.button?.trigger("click");
expect($(target).text()).toBe("help text");
});
});
47 changes: 47 additions & 0 deletions src/frontend/components/help-view/lib/component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Component } from "component";
import { MarkDown } from "util/formatters/markdown";

/**
* @class HelpView
* @extends Component
* @description A component that displays help text in a target.
*/
export default class HelpView extends Component {
// This is protected so that it can be accessed in tests.
protected $button: JQuery<HTMLAnchorElement>;
/**
* @constructor Create a new HelpView component.
* @param element The Element to attach the component to.
*/
constructor(element: HTMLElement) {
super(element);
this.initHelp(element);
}

/**
* @method initHelp
* @description Initialize the help view.
* @param element The element to attach the help view to.
*/
initHelp(element: HTMLElement) {
const $el = $(element);
const $label = $el.find("label").parent();
// Yes, I know it's not a button, but $a just didn't feel right!
const $button = $(document.createElement("a"));
if ($label && $label.length > 0) {
$button.addClass("btn").addClass("btn-plain").addClass("btn-help").attr("role", "button").attr("type", "button");
$label.first().append($button);
}
this.$button = $button;
const helpText = $el.data("help-text");
if (!helpText) throw new Error("help-text is required");
const helpTitle = $el.data("help-title");
const helpTarget = $el.data("help-target");
if (!helpTarget) throw new Error("help-target is required");
const target = document.getElementById(helpTarget);
if (!target) throw new Error(`Could not find help target with id: ${helpTarget}`);
$button.on("click", () => {
target.innerHTML = MarkDown`${helpTitle ? `### ${helpTitle}` : ""}\n${helpText}`;
});
}
}
33 changes: 33 additions & 0 deletions src/frontend/js/lib/util/formatters/markdown.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MarkDown } from "./markdown";

describe("Markdown formatter tests", ()=>{
it("Should return empty string", ()=>{
const expected = "";
const result = MarkDown``;
expect(result).toBe(expected);
});

it("Should return basic string with formatted value when no markdown is given", ()=>{
const expected = "<p>test</p>";
const result = MarkDown`test`;
expect(result).toBe(expected);
});

it("Should return properly formatted text", ()=>{
const expected = "<h1>test</h1>";
const result = MarkDown`# test`;
expect(result).toBe(expected);
});

it("Should return properly formatted text with various inputs", ()=>{
const expected = "<h1>test</h1>\n<p>test <em>test</em></p>";
const result = MarkDown`# test\ntest *test*`;
expect(result).toBe(expected);
});

it("Should return properly formatted text with newlines", ()=>{
const expected = "<h1>test</h1>\n<p>test</p>\n<p>test</p>";
const result = MarkDown`# test\\ntest\\ntest`;
expect(result).toBe(expected);
});
});
20 changes: 20 additions & 0 deletions src/frontend/js/lib/util/formatters/markdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { marked } from "marked";

type MarkdownCode = string;

type stringLike = { toString(): string };

function MarkDown(strings: TemplateStringsArray, ...values:(stringLike| string|number|MarkdownCode)[]): MarkdownCode {
marked.use({breaks: true})
let str = '';
for (let i = 0; i < strings.length; i++) {
str += strings[i];
if (i < values.length) {
str += values[i] as string ? values[i] : values[i] as MarkdownCode ? values[i] : values[i] as stringLike ? values[i].toString() : String(values[i]);
}
}
str = str.replace(/\\n/g, '\n\n');
return marked(str).trim();
}

export {MarkdownCode, MarkDown};
2 changes: 2 additions & 0 deletions src/frontend/js/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import ValueLookupComponent from 'components/form-group/value-lookup'
import MarkdownComponent from "components/markdown"
import ButtonComponent from "components/button"
import SelectAllComponent from "components/select-all"
import HelpView from "components/help-view"
import PeopleFilterComponent from "components/form-group/people-filter"

// Register them
Expand Down Expand Up @@ -76,6 +77,7 @@ registerComponent(UserModalComponent)
registerComponent(ValueLookupComponent)
registerComponent(MarkdownComponent)
registerComponent(SelectAllComponent)
registerComponent(HelpView)
registerComponent(PeopleFilterComponent)

// Initialize all components at some point
Expand Down
12 changes: 11 additions & 1 deletion views/fields/input.tt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,17 @@
[% IF ! hide_group %]
<div class="form-group">
[% END %]
<div class="input [% input_class %]">
<div class="input [% input_class %]"
[% IF data %]
[% FOREACH d IN data %]
data-[% d.key %]="[% d.value %]"
[% END %]
[%END %]
[% IF help %]
data-help-text="[% help.text %]"[% IF help.title %]
data-help-title="[% help.title %]"
[% END %]data-help-target="[% help.target %]"
[% END %]>
[%
IF label;
INCLUDE fields/sub/label_input.tt;
Expand Down
44 changes: 31 additions & 13 deletions views/view.tt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@
value = view_edit.name
placeholder = ""
filter = "html"
type = "text";
type = "text"
input_class = "help-view"
help = {
"text" => "The name of the view is used to identify it in the list of views."
"target" => "help__card"
"title" => "Name of the view"
}
%]
</div>
</div>
Expand All @@ -34,6 +40,14 @@
<div class="row">
<div class="col">
<div class="form-group">
<div class="row">
<div class="col help-view"
data-help-text="***Personal*** - available to all users and only visible to the user that creates them. **Note**: Administrators can manage other user's personal views.\n***Shared*** - users with permission to create shared views can check the shared view box and then select which groups to share the view with.\n***Administration*** - these views are only visible to Administrative users, and primarily for controlling user access to records."
data-help-title="View Type"
data-help-target="help__card">
<label>View type</label>
</div>
</div>
<div class="row">
<div class="col-lg-3">
[%
Expand Down Expand Up @@ -112,12 +126,13 @@
<div class="card__content">
<div class="row mb-3">
<div class="col">
<div class="form-group">
<div class="form-group help-view"
data-help-text='Use filters to select the records that you want to include in your view.\n#### How they Work\nFilters are created by adding rules and selecting the fields and the corresponding values that will return the records you require. Using the fundamental concept of **"AND"** and **"OR"** logic, rules can be combined to help you refine your filters based on multiple criteria.\nThis logical approach means that your views will return records where the filter rule or combination of rules and/or groups are "TRUE" statements.\n#### Examples\n***"AND":*** If you search for "Apples AND Oranges," you are looking for results that include both apples and oranges together.\n***"OR":*** If you search for "Apples OR Oranges," you are looking for results that include either apples, oranges, or both.'
data-help-title="Filters"
data-help-target="help__card">
<fieldset class="fieldset" >
<div class="fieldset__legend ">
<legend >
Filter
</legend>
<div>
<label>Filter</label>
</div>


Expand Down Expand Up @@ -174,12 +189,13 @@
<div class="card__content">
<div class="row mb-3">
<div class="col">
<div class="form-group">
<div class="form-group help-view"
data-help-title = "Filters and Grouping"
data-help-text = 'Filters and Groups allow you to construct sophisticated filters by defining specific conditions (rules) and combining them into logical groupings (groups) to precisely control which records are displayed\n#### Rules\nThese are three part statements comprised of a data field, an operator and a value.\n- ***Field*** - a drop down selector for all of the fields on your table.\n- ***Operator*** - symbols or key words to determine how the field and value interact.\n- ***Value*** - the value that is being searched for in the selected field.\n#### Groups\nThese are combinations of rules that result in a "**TRUE**" or "**FALSE**" outcome and can be combined with other rules or groups.'
data-help-target = "help__card">
<fieldset class="fieldset">
<div class="fieldset__legend">
<legend >
Field to sort the records in your view by
</legend>
<div>
<label>Field to sort the records in your view by</label>
</div>
<div class="form-group">
<div class="multiple-select">
Expand Down Expand Up @@ -392,7 +408,9 @@
<div class="col-sm-12">
<fieldset class="fieldset" >
<div class="fieldset__legend fieldset__legend--standalone">
<legend>Add fields to your view by selecting them from available fields.</legend>
<label>
<strong>Add fields to your view by selecting them from available fields.</strong>
</label>
</div>
</fieldset>
</div>
Expand Down Expand Up @@ -572,7 +590,7 @@
</h4>

<div class="card__body row">
<div class="card__content">
<div id="help__card" class="card__content">
<dl>
<dt>General</dt>
<dd>
Expand Down

0 comments on commit 227c70e

Please sign in to comment.