Professional Documents
Culture Documents
/*###########################
NAME: DWT CSS Styles
AUTHOR: Luciano Veronese
DATE: November 2022
VERSION: 2.2
############################*/
@import url("https://fonts.googleapis.com/icon?family=Material+Icons");
@import url("https://use.fontawesome.com/releases/v5.10.1/css/all.css");
/* CONFIGURATION PARAMETERS */
.StickOnTop {
/* MOST COMMONLY UPDATED */
--chevron-statusvl-alias: WFStatus;
--chevron-current-color-scheme: cs-default;
--chevron-display-for-new-records: no;
--chevron-app-alias: ;
--chevron-style: standard;
--chevron-tail-style: standard;
--chevron-tail-coloring: inherited;
--chevron-displaylayervl-alias: ;
--chevron-display-layers: ;
--chevron-divcontainer: stuck-on-top;
/* RARELY UPDATED */
--chevron-phasedone-icon: done_outline;
--chevron-checkmark_if_done: yes;
--chevron-display-status-persistent: yes;
--chevron-background: white;
--chevron-height: 3rem;
--chevron-phase-maxwidth: auto;
--chevron-phase-minwidth: auto;
--chevron-stretched-phases: 100%;
--chevron-animation-delay: 0;
--chevron-margin-top: 0px;
--chevron-margin-bottom: 5px;
--currsubphase-title-color: #000000;
--currsubphase-text-maxsize: 0.7rem;
--currsubphase-backgroundcolor: #DDDDDD;
--currsubphase-bottom-border-color: #a80520;
--popover-background-color: #f2f2f2;
--popover-title-color: #031f5a;
--popover-title-size: 1.1rem;
--popover-subtitle-color: #031f5a;
--popover-subtitle-size: 0.8rem;
--popover-subphase-color: #a80520;
--popover-subphase-size: 0.8rem;
--chevron-phasedone-color: #FFFFFF;
--phase-color-default: lightgray;
--phase-icon-size: 2.0rem;
--phase-title-maxsize: 1.1rem;
--phase-icon-color: #020202;
--phase-subphasebar-past-color: #3a3a3a;
--phase-subphasebar-current-color: #3a3a3a;
--phase-subphasebar-future-color: #3a3a3a;
}
/* COLOR THEMES */
.cs-default {
--phase-color-past: #00A357;
--phase-color-current:#176DC2;
--phase-color-future: #9E9E9E;
--phase-title-past-color: #FFFFFF;
--phase-title-current-color: #FFFFFF;
--phase-title-future-color: #212121;
--phase-subphasebar-past-color: #185c04;
--phase-subphasebar-current-color: #0c4379;
--phase-subphasebar-future-color: #333634;
--chevron-phasedone-color: #FFFFFF;
}
.cs-icystone {
--phase-color-past: #6B799E;
--phase-color-current:#EBC57C;
--phase-color-future: #a6c2ce;
--phase-title-past-color: #CCC;
--phase-title-current-color: #333;
--phase-title-future-color: #0a1136;
--phase-subphasebar-past-color: #a31800;
--phase-subphasebar-current-color: #a31800;
--phase-subphasebar-future-color: #a31800;
--chevron-phasedone-color: #FFFFFF;
}
.cs-USA {
--phase-color-past: #1F1A4F;
--phase-color-current: #C82024;
--phase-color-future: #EEEEEE;
--phase-title-past-color: #EEE;
--phase-title-current-color: #EEE;
--phase-title-future-color: #0a1136;
--phase-subphasebar-past-color: #a31800;
--phase-subphasebar-current-color: #a31800;
--phase-subphasebar-future-color: #a31800;
--chevron-phasedone-color: #EEE;
}
.cs-wild {
--phase-color-past: #68c077;
--phase-color-current: #FFD55A;
--phase-color-future: #47547e;
--phase-title-past-color: #EEE;
--phase-title-current-color: #770000;
--phase-title-future-color: #eee;
--phase-subphasebar-past-color: #a31800;
--phase-subphasebar-current-color: #a31800;
--phase-subphasebar-future-color: #a31800;
--chevron-phasedone-color: #EEE;
}
.cs-corona {
--phase-color-past: #005A9C;
--phase-color-current: #FFCB05;
--phase-color-future: #EEE;
--phase-title-past-color: #EEE;
--phase-title-current-color: #333;
--phase-title-future-color: #333;
--phase-subphasebar-past-color: #a31800;
--phase-subphasebar-current-color: #a31800;
--phase-subphasebar-future-color: #a31800;
--chevron-phasedone-color: #FFCB05;
}
.cs-greece {
--phase-color-past: #EB8F90;
--phase-color-current: #FFB471;
--phase-color-future: #ADBED2;
--phase-title-past-color: #333;
--phase-title-current-color: #770000;
--phase-title-future-color: #333;
--phase-subphasebar-past-color: #a31800;
--phase-subphasebar-current-color: #a31800;
--phase-subphasebar-future-color: #a31800;
--chevron-phasedone-color: #333;
}
.cs-macarons {
--phase-color-past: #B7DDE0;
--phase-color-current: #FFD0D6;
--phase-color-future: #FEE19F;
--phase-title-past-color: #333;
--phase-title-current-color: #770000;
--phase-title-future-color: #333;
--phase-subphasebar-past-color: #a31800;
--phase-subphasebar-current-color: #a31800;
--phase-subphasebar-future-color: #a31800;
--chevron-phasedone-color: #333;
}
.cs-jeweldark {
--phase-color-past: #2B628B;
--phase-color-current: #92363B;
--phase-color-future: #A87932;
--phase-title-past-color: #DDDDDD;
--phase-title-current-color: #DDDDDD;
--phase-title-future-color: #DDDDDD;
--phase-subphasebar-past-color: #a31800;
--phase-subphasebar-current-color: #a31800;
--phase-subphasebar-future-color: #a31800;
--chevron-phasedone-color: #DDDDDD;
}
/*### END OF CSS USER DEFINED CONFIG ###*/
.DWTContainer {
position: relative;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center; /*center, flex-start, flex-end*/
min-width: fit-content;
margin-right: 0; /* CHANGE calc(var(--chevron-height) / 2); Zero if flat tail
*/
margin-top: var(--chevron-margin-top);
margin-bottom: var(--chevron-margin-bottom);
}
.DWTPhaseContainer {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: var(--chevron-stretched-phases);
}
.DWTPhase {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: var(--chevron-stretched-phases);
max-width: var(--chevron-phase-maxwidth);
min-width: var(--chevron-phase-minwidth);
height: var(--chevron-height);
background-color: var(--phase-color-default);
color: black;
cursor: help;
}
.DWTPhase::before {
position: absolute;
content: "";
width: 0px;
height: 0px;
border-left: calc(var(--chevron-height) / 2) solid var(--chevron-background);
border-right: 0px solid transparent;
border-bottom: calc(var(--chevron-height) / 2) solid transparent;
border-top: calc(var(--chevron-height) / 2) solid transparent;
left: 0%;
}
.DWTPhase[isChevronFlat='true']::before {
position: absolute;
content: "";
width: 0px;
height: 0px;
border: none;
left: 0%;
}
.DWTPhase[isTailFlat='false']::after {
position: absolute;
content: "";
width: 0px;
height: 0px;
left: 100%;
border-left: calc(var(--chevron-height) / 2) solid var(--phase-color-default);
border-right: 0px solid transparent;
border-bottom: calc(var(--chevron-height) / 2) solid transparent;
border-top: calc(var(--chevron-height) / 2) solid transparent;
z-index: 1;
}
.DWTPhase[isChevronFlat='true']::after {
position: absolute;
content: "";
width: 0px;
height: 0px;
left: 100%;
border: none!important;
z-index: 1;
}
.DWTPhaseLabel {
display: flex;
margin-left: calc(var(--chevron-height) / 2);
}
.DWTPhaseLabelIcon {
display: flex;
align-items: center;
justify-content: center;
max-width: 1rem;
}
.DWTPhaseLabelIcon .material-icons {
font-size: clamp(0.3rem, 0.6vw + 1rem, var(--phase-icon-size));
color: var(--phase-icon-color);
/*padding: 0.3em;*/
}
.DWTPhaseLabelText {
display: flex;
align-items: center;
justify-content: center;
padding-left: 0.5rem;
color: var(--phase-title-color);
font-size: clamp(0.3rem, 0.5vw + 0.6rem, var(--phase-title-maxsize));
}
.DWTPhaseContainer:not(:first-child) .DWTPhaseLabel {
}
.DWTPhaseContainer:nth-child(1) .DWTPhase[Status='P']::before,
.DWTPhaseContainer:nth-child(1) .DWTPhase[Status='C']::before {
display: none;
border-right: 0;
border-left: 0;
border-top: 0;
}
.DWTPhase[Status='P'] {
background-color: var( --phase-color-past, lightgray);
}
.DWTPhase[Status='P']::after {
border-left: calc(var(--chevron-height) / 2) solid var( --phase-color-past,
lightgray);
}
.DWTPhase[Status='C'] {
background-color: var( --phase-color-current, lightgray);
}
.DWTPhase[Status='C']::after {
border-left: calc(var(--chevron-height) / 2) solid var( --phase-color-current,
lightgray);
}
.DWTPhase[Status='F'] {
background-color: var( --phase-color-future, lightgray);
}
.DWTPhase[Status='F']::after {
border-left: calc(var(--chevron-height) / 2) solid var( --phase-color-future,
lightgray);
}
/* Icon and label colors should be the same. If specific colors are not define,
fall back to a default */
.DWTPhase[Status='P'] .DWTPhaseLabel .DWTPhaseLabelText, .DWTPhase[Status='P'] .DWT
PhaseLabel .DWTPhaseLabelIcon .material-icons {
color: var( --phase-title-past-color, var(--phase-icon-color));
margin-left: 0.8rem;
}
.DWTPhase[Status='C'] .DWTPhaseLabel .DWTPhaseLabelText, .DWTPhase[Status='C'] .DWT
PhaseLabel .DWTPhaseLabelIcon .material-icons {
color: var( --phase-title-current-color, var(--phase-icon-color));
}
.DWTPhase[Status='F'] .DWTPhaseLabel .DWTPhaseLabelText, .DWTPhase[Status='F'] .DWT
PhaseLabel .DWTPhaseLabelIcon .material-icons {
color: var( --phase-title-future-color, var(--phase-icon-color));
}
.DWTSubPhaseContainer {
position: relative;
display: flex;
flex-wrap: nowrap;
justify-content: center; /* Chevron alignment when not stretched: center, flex-
start, flex-end*/
align-items: center;
flex-direction: column;
width: 100%;
}
.DWTSubphaseBar {
display: flex;
width: 99.0%;
min-height: 0.2rem;
margin-top: -0.2rem;
border: 0;
border-top-left-radius: 2rem;
border-top-right-radius: 2rem;
}
.DWTSubphaseBar[Status='P'] {
background-color: var(--phase-subphasebar-past-color);;
}
.DWTSubphaseBar[Status='C'] {
background-color: var(--phase-subphasebar-current-color);;
}
.DWTSubphaseBar[Status='F'] {
background-color: var(--phase-subphasebar-future-color);;
}
.DWTCurrentSubphase {
color: var(--currsubphase-title-color);
font-size: clamp(0.3rem, 0.4vw + 0.4rem, var(--currsubphase-text-maxsize));
margin-top: 0px;
z-index: 1;
min-height: .8em;
width: auto;
background-color: var(--currsubphase-backgroundcolor);
padding: 0 4px 0 4px;
border-bottom: 3px solid;
border-bottom-color: var(--currsubphase-bottom-border-color);
pointer-events: none;
}
summary {
cursor: pointer;
outline: none;
font-size: 1.7em;
line-height: 0.3;
vertical-align: bottom;
position: absolute;
z-index:1;
}
summary:hover {
color: #AA0000;
}
summary::marker {
display: none;
}
summary::after {
content: "+";
line-height: 1.2;
color: #AAA;
float: left;
font-size: 1.2em;
font-weight: bold;
margin: -10px 10px 0 0;
padding: 0;
text-align: center;
width: 5px;
}
details[open] summary {
margin-top: 1.5rem; /* Margin from top of the controller */
}
details[open] summary::after {
content: "=";
}
details[open] summary::maker {
margin-top: 2rem;
}
details[open] summary ~ * {
animation: sweep 0.5s ease-in-out;
}
@keyframes sweep {
0% {opacity: 0;}
100% {opacity: 1;}
}
#DWTMessage {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
color: white;
font-size: 1.3rem;
padding: 0.2rem;
}
.tippy-box[data-theme~='custom'] {
background-color: var(--popover-background-color);
color: black;
font-family: Raleway, sans-serif;
font-size: 18px;
border-top: 1px solid #888;
border-bottom: 1px solid #888;
z-index: 2;
}
.tooltip-title {
color: var(--popover-title-color);
font-size: var(--popover-title-size);
text-align: center;
display: block;
padding-top: 5px;
}
.tooltip-subtitle {
color: var(--popover-subtitle-color);
font-size: var(--popover-subtitle-size);
text-align: left;
display: block;
padding-top: 5px;
}
.tooltip-subphases {
color: var(--popover-subphase-color);
font-size: var(--popover-subphase-size);
margin-top:0;
text-align: left;
display: block;
padding-top: 2px;
}
@-webkit-keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
@-webkit-keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}
/* Display Layer icon overlayed on top the chevrons */
.DWTDisplayLayerContainer {
width: 1.3rem;
height: 1.3rem;
background-color: rgb(211, 194, 6);
margin-bottom: 0.2rem;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
border-radius: 50%;
position:absolute;
z-index: 2;
top: 0.2rem;
right: 0rem;
display: none;
}
.DWTDisplayLayerTag {
width: 1.1rem;
height: 1.1rem;
background-color: rgb(211, 194, 6);
border-radius: 50%;
color: darkred;
font-weight: bold;
font-size: 1rem;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
</style>
<script src="https://unpkg.com/@popperjs/core@2"></script>
<script src="https://unpkg.com/tippy.js@6"></script>
<script>
/
*##################################################################################
###
NAME: DWT - Dynamic Workflow Tracker
AUTHOR: Luciano Veronese
DATE: November 2022
VERSION: 2.2
DESCRIPTION: display a visual representation of the main phases of a workflow
whose
phases are described by a values list. The values list can be hierarchical (2
levels only)
The second level is aimed to describe sub-phases of a given phase. This is useful
to
describe the source phase when landing into a given phase (the direction we are
coming from)
The custom object is fully customizable through CSS properites and supports
popovers
Popovers are configured through a JSON content dropped into the Description field
of the
values list items. Please, refer to the documentation for the supported
properties.
Material Design Icons are available here: https://fonts.google.com/icons
CHANGELOG:
1.0 - Initial release, restricted user testing
1.1 - Bug fix: Action dropdown hidden by DWT
1.2 - Bug fix: collapse did not minimize
1.3 - Bug Fix: toolbar dropdown menus hidden by DWT. Now DWT can coexist with the
ARCO COs
2.0 - Changed the API communication layer (faster)
- The DWT can now be displayed for new record
- Improved error checking and display
- Fixed bug that duplicated the DWT when tabs in form and switched tab
- The collapse/expand status can be configured to be persistent (cached)
- The color of the subphase bar can be configured
- Flat tail: the last phase chevron can now be flat instead of arrow-spaed
- Display Layers: phases can now be associated to Layers (numbers) and
selectectively displayed in a static or dynamic way
- The active phase for new records can be set through the default item set in
the VL
2.1 - Introduced the management of Layers for sub-phases and theme-specific
subphase colors
2.2 - Name changed from CWT to DWT (Dynamic Workflow Tracker)
- Fixed a visualization bug (checkmark of last chevron)
- Added new option to replace chevrons (triangles) with vertical lines
(separators)
- Added the possibility to instanciate multiple trackers in the same layout
###################################################################################
##*/
// The Custom Object code is wrapped by the module design pattern to bound the
namespace
// Only the DisplayDWT function is made availble outside the module
(DWT.DisplayDWT())
// Constants
const DWT_STYLE_STANDARD="standard"
const DWT_STYLE_FLAT="flat"
const DWT_CACHE_BASE = "DWTCACHE-v1-" // Name of the cached values in the web
broser local storage
const STS_Past = "P"
const STS_Current = "C"
const STS_Future = "F"
const DEFAULT_MESSAGE_BKGCOLOR = '#990000'
const ARCHER_TOP_DIV = "toolbar-app-buttons" // Append below toolbar
const DEFAULT_VALUES_LIST = "WFStatus";
const DIV_ID_STICKONTOP = "StickOnTop"
/*#################################################################################
####
The following functions are wrappers of the REST APIs aimed to allow reading
the
values list with ths chevron status.
###################################################################################
##*/
const FetchRequestTemplate = {
mode: 'cors',
cache: 'no-cache',
headers: {
'Cache-Control': 'no-cache',
// 'accept':
'application/json,text/html,application/xhtml+xml,application/xmlq=0.9,*/*;q=0.8',
'accept':
'application/json,text/html,application/xhtml+xml,application/xmlq=0.9,/;q=0.8',
'content-type': 'application/json; charset=utf-8'
}
}
class ArcherSessions {
constructor(Scope, Baseurl, csrfToken, SessionToken) {
this.Scope = Scope
this.Baseurl = Baseurl
this.csrfToken = csrfToken
this.SessionToken = SessionToken
this.AppList = []
this.metaCache = null
this.context = {
RecordId: 0,
AppAlias: "",
StatusFieldAlias: "",
DisplayLayerAlias: "",
DisplayStatusPersistent: null,
SummaryDetailsDiv: null, // summary/details element to
collapse/expand the DWT
ChevronStyle: "",
TailStyle: "",
DisplayForNewRecords: "",
CheckmarkIfDone: false,
DefaultPhaseLayer: [],
PhaseLayer: [],
DWTcontainerEl: null
}
}
}
class VLItems {
constructor(Name, NameId, ParentName, ParentId, Description, NumericValue,
SortOrder, IsActive, IsDefault) {
this._Name = Name
this._NameId = NameId
this._ParentName = ParentName
this._ParentId = ParentId
this._Description = Description
this._NumericValue = NumericValue
this._SortOrder = SortOrder
this._IsActive = IsActive
this._IsDefault = IsDefault
this._Status = ''
this._Step = null
this._SubStep = null
this._IsFirst = false
this._IsLast = false
this._LayerMatch = false
}
get Name() {
return (this._Name)
}
set Name(name) {
this._Name = name
}
get NameId() {
return (this._NameId)
}
set NameId(name) {
this._NameId = name
}
get ParentName() {
return (this._ParentName)
}
set ParentName(pname) {
this._ParentName = pname
}
get ParentId() {
return (this._ParentId)
}
set ParentId(pid) {
this._ParentId = pid
}
get Description() {
return (this._Description)
}
set Description(desc) {
this._Description = desc
}
get NumericValue() {
return (this._NumericValue)
}
set NumericValue(nv) {
this._NumericValue = nv
}
get SortOrder() {
return (this._SortOrder)
}
set SortOrder(so) {
this._SortOrder = so
}
get IsActive() {
return (this._IsActive)
}
set IsActive(ia) {
this._IsActive = ia
}
get IsDefault() {
return (this._IsDefault)
}
set IsDefault(idef) {
this._IsDefault = idef
}
get Status() {
return (this._Status)
}
set Status(sts) {
this._Status = sts
}
get Step() {
return (this._Step)
}
set Step(st) {
this._Step = st
}
get SubStep() {
return (this._SubStep)
}
set SubStep(ss) {
this._SubStep = ss
}
get IsFirst() {
return (this._IsFirst)
}
set IsFirst(sts) {
this._IsFirst = sts
}
get IsLast() {
return (this._IsLast)
}
set IsLast(sts) {
this._IsLast = sts
}
get LayerMatch() {
return (this._LayerMatch)
}
set LayerMatch(sts) {
this._LayerMatch = sts
}
}
//--- AddSessionTokenToHeaders ---
// Helper function to build the http header
function AddSessionTokenToHeaders(ThisArcherSession, FetchRequest) {
// Depending on the type of session, att the proper authorization token
if (ThisArcherSession.Scope == 'Internal') {
FetchRequest.headers = {
...FetchRequest.headers,
...{
'x-csrf-token': ThisArcherSession.csrfToken
}
}
} else {
FetchRequest.headers.Authorization = 'Archer session-id="' +
ThisArcherSession.SessionToken + '"'
}
return
}
return responseJ
}
//------------jsaGetLevelIdFromFieldAliasAndAppId ------------
// Get the Level Id starting from the Application Id and Field Alias
const jsaGetLevelIdFromFieldAliasAndAppId = async (ThisArcherSession,
FieldAlias, AppId) => {
if (!FieldAlias || !AppId)
throw new Error(`Wrong Field Alias (${FieldAlias}) or Application Alias
(${AppId})`)
let endpoint =
`api/core/system/fielddefinition/level/${LevelId}/valueslist`
let requestbody = `?$filter=Alias eq '$
{VLAlias}'&$select=Type,Id,LevelId,RelatedValuesListId`
let responseJ = await jsaPOSTwithGEToverride(ThisArcherSession, endpoint,
requestbody)
if (responseJ[0].RequestedObject.Type != 4) // If the field is not a values
list... error!
throw new Error(`The selected Status Field is not of type Values List`)
return {
ValuesListId: responseJ[0].RequestedObject.RelatedValuesListId,
VLFieldId: responseJ[0].RequestedObject.Id
}
}
if (!response.ok)
throw new Error(`[jsaGetVLItemsFromVLId] - Fetch error "$
{ response.status})"`)
if (!response.ok)
throw new Error(`[jsaGetLevelIdFromRecordId] - Fetch error "$
{ response.status})"`)
return (responseJ.RequestedObject.LevelId)
}
try {
// The LevelId and the Field Alias are necessary to read the VL data
LevelId= await jsaGetLevelIdFromRecordId(MyArcherSession,
ContentRecordId)
// Get the VLId along with the FieldId (used later)
try {
response = await jsaGetVLIdFromLevelIdAndVLAlias(MyArcherSession,
LevelId, PhaseLayerFieldAlias)
} catch (error) {
throw new Error(`Display Layer VL Alias "${PhaseLayerFieldAlias}"
not found in the application, check the configuration`)
}
const {ValuesListId, VLFieldId} = response
//console.log(`%c[jsaGetVLItemNumbers] - VLID="${ValuesListId}" -
FieldId="${VLFieldId}" - VL ITEM MAP`, "color: #CC0000", vl_items)
// Build the Display Layers array to hand over to the next functions
let Numbers = []
if (resp.length == 1 && resp[0].IsSuccessful) {
let ro =
resp[0].RequestedObject.FieldContents[VLFieldId.toString()]
if (ro != null && ro.Type != 4){
throw new Error(`The Field with id="${VLFieldId}" is not a
values list`)
}
if (ro.Value != null && ro.Value.ValuesListIds.length>0) { // At
least one level is selected in the values list
let vlids = [...ro.Value.ValuesListIds] // array of VLIds
let index
for (index=0; index<ro.Value.ValuesListIds.length; index++) {
let item = vl_items.get(vlids[index])
if (typeof item !== 'undefined')
Numbers.push(item.NumericValue)
}
}
}
return (Numbers)
} catch(err) {
throw new Error(err.message)
}
}
//------------jsaGetStatusVLDefinition ------------
// Read the structure of a values list (list of items) organized as one or two
levels
// The function wraps several of the previous calls and is aware of the content
record status
// If the record is new (never saved) the Application Alias and Field Alias are
necessary
// to derive the information to pull the VL data structure (items)
// If the record has been saved at least once, only the Field Alias is
necessary
const jsaGetStatusVLDefinition = async (MyArcherSession) => {
let LevelId=null, AppId=null, response=null, vl_items=null
// This information is included in the session context to easy its
transport across the functions
let ContentRecordId=MyArcherSession.context.RecordId
let AppAlias=MyArcherSession.context.AppAlias
let StatusFieldAlias=MyArcherSession.context.StatusFieldAlias
try {
// The LevelId and the Field Alias are necessary to read the VL data
// but pulling the Level Id is different depending on the content
record status (new or saved)
if (ContentRecordId != 0) {
// The record has been saved at least once, so it has a RecordId
LevelId= await jsaGetLevelIdFromRecordId(MyArcherSession,
ContentRecordId)
console.log(`EXISTING RECORD - Level Id "${LevelId}" read from
Record Id "${ContentRecordId}"`)
} else {
// The record has not yet been saved, so the Record Id is 0. In
this case, the App Alias is also needed
if (!AppAlias)
throw new Error(`The Application Alias config parameter is
required to display the DWT for new records`)
try {
AppId = await jsaGetAppIdFromAppAlias(MyArcherSession,
AppAlias)
//console.log(`[jsaGetStatusVLDefinition] - Application Id "$
{AppId}"`)
} catch (error) {
// If an error happens it's likely to be a wrongly configured
App Alias
throw new Error(`Application Alias "${AppAlias}" not found in
the Archer application, check the configuration`)
}
try {
LevelId = await
jsaGetLevelIdFromFieldAliasAndAppId(MyArcherSession, StatusFieldAlias, AppId)
console.log(`%cNEW RECORD detected - Level Id "${LevelId}" read
from App Alias "${AppAlias}"`, "color: #00AA00")
} catch (error) {
// If an error happens it's likely to be a wrongly configured
Status VL Alias
throw new Error(`Status VL Alias "${StatusFieldAlias}" not
found in the application, check the configuration`)
}
}
// The Status Field Alias is needed for both record conditions
// The function returns the VLId along with the FieldId (used later)
try {
response = await jsaGetVLIdFromLevelIdAndVLAlias(MyArcherSession,
LevelId, StatusFieldAlias)
} catch (error) {
throw new Error(`Status VL Alias "${StatusFieldAlias}" not found in
the application, check the configuration`)
}
return ({
StatusFieldId: VLFieldId, // This is needed later
ValuesListItems: vl_items
})
} catch(err) {
throw new Error(err.message) // Rethrow new Error
}
}
// Select the values list IDs to pull along with the specific content
record
contentRequest.body = JSON.stringify({
FieldIds: [VLFieldId.toString()],
ContentIds: [ThisArcherSession.context.RecordId]
})
//------------jsaGetStatusVLDefinitionAndSelectedItem ------------
// Read the VL definition and the selected item (status to display) considering
also the record status
// Is the record has been saved, the selected item corresponds to the selected
status fo the VL
// If the record is new, the selected item is the first in the VL structure
const jsaGetStatusVLDefinitionAndSelectedItem = async (ThisArcherSession) => {
// Get the definition (map) of the Values List Items and a selection of
their attributes
const {StatusFieldId, ValuesListItems} = await
jsaGetStatusVLDefinition(ThisArcherSession)
// Enrich each object in the map with the name of the parent values list
for (const k of ValuesListItems.keys()) {
let pid = (ValuesListItems.get(k)).ParentId
ValuesListItems.get(k).ParentName = pid != null ?
ValuesListItems.get(pid).Name : ""
if (iterator++ == 0)
FirstVLItem = ValuesListItems.get(k) // Save the first item to use
when RecordId=0
if (ValuesListItems.get(k).IsDefault) {
FirstDefaultVLItem = ValuesListItems.get(k)
DefaultItemFound = true
}
}
// The list of items is returned for new and saved records, but the
selected item
// can be returned only if the record has been saved at least once
if (ThisArcherSession.context.RecordId != 0) {
if (ThisArcherSession.context.DisplayLayerAlias != "") {
// DYNAMIC DISPLAY LEVELS: the PhaseLayer VL alias is set, so the
"static" approach is ignored
// Fetch the Display Layers set via the VL (Dynamic Display Layers)
ThisArcherSession.context.PhaseLayer = await
jsaGetVLItemNumbers(ThisArcherSession, ThisArcherSession.context.DisplayLayerAlias)
// If no layers are selected via the VL, revert back to the static
configuration
if (ThisArcherSession.context.PhaseLayer.length == 0) {
ThisArcherSession.context.PhaseLayer =
ThisArcherSession.context.DefaultPhaseLayer
console.warn(`No dynamic layers selected, layers pulled from
static config (${ThisArcherSession.context.PhaseLayer}). If empty, all the phases
are displayed by default.`)
}
}
// Read the currently selected status VL item
let jresp = await jsaGetVLSelectedItems(ThisArcherSession,
StatusFieldId)
(jresp['0'].RequestedObject.FieldContents[index].Value.ValuesListIds).forEach(
(vlid) => {
let tobj = ValuesListItems.get(vlid)
let itm = new VLItems(tobj.Name, tobj.NameId,
tobj.ParentName, tobj.ParentId, tobj.Description, tobj.NumericValue,
tobj.SortOrder, tobj.IsActive, tobj.IsDefault)
SelectedItems.push(itm)
}
)
// An object containing the VL definition and the selected items is
returned
return ({
AllItems: ValuesListItems,
SelectedItems: SelectedItems,
LastUpdateStr:
jresp['0'].RequestedObject.UpdateInformation.UpdateDate
})
} else {
throw new Error(`No item selected in Status Values List "$
{ThisArcherSession.context.StatusFieldAlias}"`)
}
} else { // The record is new, that is it's not yet been saved
let tobj
// If an item set as default is found, then that is set as selected
item, otherwise the first is used
if (DefaultItemFound)
tobj = FirstDefaultVLItem
else
tobj = FirstVLItem
let itm = new VLItems(tobj.Name, tobj.NameId, tobj.ParentName,
tobj.ParentId, tobj.Description, tobj.NumericValue, tobj.SortOrder, tobj.IsActive,
tobj.IsDefault)
SelectedItems.push(itm)
return ({
AllItems: ValuesListItems,
SelectedItems: SelectedItems,
LastUpdateStr: null
})
}
}
//#################################################################################
####
// DWT HELPER FUNCTIONS
//#################################################################################
####
Spinner.show = function () {
if (Spinner == null || Spinner.svgEl == null)
return
Spinner.svgEl.classList.add("DWTspinner")
Spinner.svgEl.style.display = "block"
}
Spinner.hide = function () {
if (Spinner == null || Spinner.svgEl == null)
return
Spinner.svgEl.classList.remove("DWTspinner")
Spinner.svgEl.style.display = "none"
}
//------------DWTGetConfigParam ------------
// Reads the config param defined by a CSS variable.
// Returns null is the input params are wrong
// Returns "" is the variable does not exist
function DWTGetConfigParam(containerEl, cssvariable) {
if ((containerEl !== null) && (cssvariable !== ""))
return
window.getComputedStyle(containerEl).getPropertyValue(cssvariable).trim()
else
return null
}
// This flag sets an attribute that prevents the last chevron triangle to
display
let FlatTailFlag = (ThisArcherSession.context.TailStyle == DWT_STYLE_FLAT
&& CurrentPhase.Core.IsLast)? "true":"false"
// Flag to set they style of the chevron (except the tail) which can
standard (triangle) or flat (vertical line)
let isChevronFlat = (ThisArcherSession.context.ChevronStyle ==
DWT_STYLE_FLAT)? "true":"false"
// This flag is aimed to detect when the WF is complete, which means the
last phase has status=current
let Completed = (CurrentPhase.Core.Status == STS_Current &&
CurrentPhase.Core.IsLast)?true:false
// Based on the configuration, set the condition to coloring the last phase
when the WF is completed
let IfCompletedInheritLastPhaseStatus = (DWTGetConfigParam(DWTcontainerEl,
"--chevron-tail-coloring") == "inherited") && Completed?true:false
// Put the checkmark on past phases. If the phase is the last (completed),
put a checkmark as well
if (CurrentPhase.Children.length > 0) {
// Setup the initial markup for the children
mk += `<div class="DWTSubPhaseContainer"><div class="DWTSubphaseBar"
status="${IfCompletedInheritLastPhaseStatus?STS_Past:CurrentPhase.Core.Status}"></
div>`
for (let idx=0; idx<CurrentPhase.Children.length; idx++) { // ForEach
did not work here..
let child = CurrentPhase.Children[idx]
if (child.Status == STS_Current && child.LayerMatch) {
if (!child.IsActive) // Do not render sub-phases whose values
list items are disabled or whose layer is not active
return ("")
let spoverride = GetDescriptionOption(child.Description,
"spoverride")
// Check if an override string exists for the current sub-base
if (spoverride == "") {
// No, use the name of sub-phase
mk += `<div class="DWTCurrentSubphase">${child.Name}</div>`
} else {
// Yes, use the override string
mk += `<div class="DWTCurrentSubphase">${spoverride}</div>`
}
}
}
mk += `</div>`
}
mk += `</div>`
return mk
}
//------------DWTDisplayPhase ------------
// Callback for the animated rendering
function DWTDisplayPhase(item) {
if (item == null) return
item.style.visibility = "visible"
}
let renderedPhaseNumber = 0
wfphases.push({
Core: item,
Children: new Array(),
})
} else {
// This is a children (sub state)
if (item.SortOrder == SelectedItems.SortOrder) {
// Selected (Current) sub state
SelectedItems.Step = wfstep
SelectedItems.SubStep = SubStep
SelectedItems.Status = STS_Current
CurrentStatusParent.Status = STS_Current // Set to active also
the parent state of the selected item
}
// Check if parent of this children item is disabled. If yes, the
chindren item must be disabled as well
let myparent = AllItems.filter(element => {
if ((item.ParentId == element.NameId))
return (element)
})
if (myparent[0].IsActive && !item.IsActive) // Phase active, sub-
phase not active
item.IsActive = false // Sub-phase wins
if (!myparent[0].IsActive) // If Phase is not active
item.IsActive = false // Sub-phases are disabled as well
// Calculate the step numbering to identify the items and their
order
item.Step = wfstep
item.SubStep = SubStep++
//============================================
// BUILD THE MARKUP to display the DWT phases
let markup, phaseMarkup=""
// This init markup is about the tooltip
markup = `<details id="DetailsOf-${DWTcontainerEl.id}"><summary></summary>
<div class="DWTContainer">`
DWTcontainerEl.innerHTML = markup
// Build the markup of each phase in the map
markup += `${wfphases.map((CurrentPhase) => {
phaseMarkup=DWTRenderPhase(ThisArcherSession, CurrentPhase,
DWTcontainerEl)
if (phaseMarkup!="") renderedPhaseNumber++
return phaseMarkup
}).join("")}`
markup += `</div></details>`
// Render in the DOM the generated markup, but not display it yet
DWTcontainerEl.style.visibility = "hidden"
DWTcontainerEl.innerHTML = markup
// The popover markup is dynamically built pulling the content from the
values list items
// The markup is dynamically added buy using the Tippy open source library
wfphases.forEach((phase) => {
let PhaseSelectorId = `[Id="Step${phase.Core.Step}.$
{phase.Core.SubStep}"]`
let PhaseEl = DWTcontainerEl.querySelectorAll(PhaseSelectorId)
if (PhaseEl != null) {
let contentmk = `<span class="tooltip-title">$
{GetDescriptionOption(phase.Core.Description,
"titleprefix")}${phase.Core.Name}</span>`
contentmk +=`<span class="tooltip-subtitle">$
{GetDescriptionOption(phase.Core.Description, "subtitle")}</span>`
if (phase.Children.length > 0) {
contentmk += `<ul class="tooltip-subphases">`
phase.Children.forEach((child) => {
if (child.LayerMatch)
contentmk += `<li>${child.Name}</li>`
})
contentmk += `</ul>`
}
tippy(PhaseEl, {
content: contentmk,
trigger: 'click',
arrow: true,
allowHTML: true,
animation: 'fade',
duration: [550, 200],
theme: 'custom',
distance: 10,
interactive: true,
placement: 'top',
showOnInit: true,
})
}
})
}
let DWTWrapperEl=DWTcontainerEl.getElementsByClassName("DWTContainer")
if (DWTWrapperEl != null) {
// Adapt the right margin depending on the tail style, so that the DWT
is always centered
DWTcontainerEl.getElementsByClassName("DWTContainer")
[0].style.marginRight=ThisArcherSession.context.TailStyle ==
DWT_STYLE_FLAT?"0":"calc(var(--chevron-height) / 2)"
// Apply a left margin to the label of the first phase, so that it does
not overlap with the collaps/expand button
DWTcontainerEl.getElementsByClassName("DWTContainer")
[0].firstChild.getElementsByClassName("DWTPhase")
[0].firstChild.style.marginLeft="1.8rem"
}
}
// Attach the newly created container to the top of the record page
let hookEl = document.getElementById(ARCHER_TOP_DIV)
if (ARCHER_TOP_DIV == "master_windowContainer" )
hookEl.prepend(containerEl)
else
hookEl.append(containerEl)
} else {
// Retrieve through the CSS variables the name of the target div
container
containerEl = document.getElementById(targetContainerId)
if (containerEl == null) {
console.log(`%cWARNING: Div container "${targetContainerId}" not
found in any C.O in the layout. It might be hiddent in a non-selected tab or check
the JS configuration. Exiting..."`,"color: #CC0000")
return
}
// Set the class to the same container Id name, so that the CSS
variable scope is set,
// and the correct variables can be found
containerEl.className = targetContainerId
}
return (containerEl)
}
//-----------------------------------------------------------------------------
--------
// DisplayDWT: main function to display the Workflow Tracker
//-----------------------------------------------------------------------------
--------
const DisplayDWT = async (pDWTStatusFieldAlias=null, DWTcontainerEl=null) => {
// Get some configuration parameters from the CSS styles in the current
namespace
DisplayForNewRecords = DWTGetConfigParam(DWTcontainerEl, "--chevron-
display-for-new-records") == "no"?"no":"yes" // Default: "yes"
// If input params is not specified, grab it from the CSS property. set a
default- grab them from the CSS variables. This is aimed to
// avoid changing the custom object code and configure it via the CSS
variable only
if (pDWTStatusFieldAlias == null) {
let VLalias = DWTGetConfigParam(DWTcontainerEl, "--chevron-statusvl-
alias")
VLalias == ""?DWTStatusFieldAlias =
DEFAULT_VALUES_LIST:DWTStatusFieldAlias = VLalias
}
else {
DWTStatusFieldAlias = pDWTStatusFieldAlias
}
console.log(`%cConfigured App VL Alias="${DWTAppAlias}" - Status VL Alias
"${DWTStatusFieldAlias}"`, "color: #00AA00")
// The record is new, but the configuration does not require to display the
DWT, so the custom object immediately terminates
if (ThisContentRecordId==0 && DisplayForNewRecords=="no") {
console.log(`%cNew Content Record DETECTED, but config param "--
chevron-display-for-new-records" is set to "no". Silently exiting...`,"color:
#DD0000")
return
}
// Get the Display Layers to display, converting the list into an array of
integers, filtering out strings
DWTDispLayAlias = DWTGetConfigParam(DWTcontainerEl, "--chevron-
displaylayervl-alias")
DefaultPhaseLayer = (DWTGetConfigParam(DWTcontainerEl, "--chevron-display-
layers")).split(',').map(Number).filter(Boolean)
if (DefaultPhaseLayer!="")
console.log(`%cSTATIC Display Layers (${DefaultPhaseLayer})
configured`, "color: #00AA00")
if (DWTDispLayAlias!="")
console.log(`%cDYNAMIC Display Layers configured (pulled from VL alias
"${DWTDispLayAlias}")`, "color: #00AA00")
if (DefaultPhaseLayer+DWTDispLayAlias == "")
console.log(`%cNo Display Layers configured`, "color: #00AA00")
// To render the arrows in the DWT the "CSS triangle pattern" is used, but
this uses the zIndex to overlam the triangle
// However, the AWF and Toolbar dropdowns have a zIndex=1 which cannot be
changed and this the triangle overlaps the manu
// The following code raises on the fly the zIndex of the dropdowns so that
they are correctly displayed
// A Timeout is used as an asych function to run after the custom object
"ends" because the toolbar is rendered after the CO is run
const zIndexRaiseTargets=['workflow-dropdown-content', 'share-dropdown-
content', 'ellipsis-dropdown-content']
setTimeout(() => {
zIndexRaiseTargets.forEach ((targetEl) => {
const sel = document.getElementById(targetEl)
if (sel != null)
sel.style.zIndex=2
})
}, 1000)
Spinner(DWTcontainerEl)
Spinner.show()
try {
console.log(`Connecting to the Archer API framework`)
MyArcherSession = ConnectToArcher()
// The below information must be available in multiple areas, so a
context object is defined
// and added to the session so that it can be transported across the
functions easier (convoy!)
MyArcherSession.context.RecordId = ThisContentRecordId
MyArcherSession.context.AppAlias = DWTAppAlias
MyArcherSession.context.DisplayLayerAlias = DWTDispLayAlias
MyArcherSession.context.StatusFieldAlias = DWTStatusFieldAlias
MyArcherSession.context.DisplayForNewRecords = DisplayForNewRecords
MyArcherSession.context.DefaultPhaseLayer = DefaultPhaseLayer
MyArcherSession.context.PhaseLayer = DefaultPhaseLayer // Set current =
default
MyArcherSession.context.DisplayStatusPersistent =
DWTGetConfigParam(DWTcontainerEl, "--chevron-display-status-persistent")
MyArcherSession.context.TailStyle = DWTGetConfigParam(DWTcontainerEl,
"--chevron-tail-style") == "flat"?DWT_STYLE_FLAT:DWT_STYLE_STANDARD // Default
"DWT_STYLE_STANDARD"
MyArcherSession.context.ChevronStyle =
DWTGetConfigParam(DWTcontainerEl, "--chevron-style") == "flat"?
DWT_STYLE_FLAT:DWT_STYLE_STANDARD // Default "DWT_STYLE_STANDARD"
MyArcherSession.context.CheckmarkIfDone =
DWTGetConfigParam(DWTcontainerEl, "--chevron-checkmark_if_done")=="yes"?true:false
MyArcherSession.context.DWTcontainerEl = DWTcontainerEl
// Fetch the defined DWT phases and the selected status fetiching them
form the Status VL
jresp = await jsaGetStatusVLDefinitionAndSelectedItem(MyArcherSession)
DLContainer.style.display=['','none'].includes(DLContainer.style.display)?"flex":"n
one"
})
}
})
return
}
return {
// Publicly available methods
GetTargetContainerEl,
DisplayDWT
}
})() // End of module