Task Form Decoration
Task Form Decoration 202208 / 処理フォーム画面(タスクForm)のデコレーション 202208

Task Form UI/UX design is extremely important for improving business processes. These are examples of Task Form decorations for workflow app administrators. Workflow app administrators are free to use them for free, commercial or non-commercial, without worrying about licensing restrictions. Please feel free to use these code examples. (Note that you do so at your own risk, low-code programming skills in JavaScript are required.)

タスク処理画面のUI/UXデザインは、ビジネスプロセス改善において極めて重要です。以下は、処理画面(タスクフォーム)のデコレーション例です。ワークフローアプリ管理者は、ライセンス上の制限を気にすることなく、商用非商用問わず、無料で自由に利用できます。(ただしデコレーションは自身の責任で行ってください。また JavaScript プログラミング知識が必要です)

The following code examples are written in “Questetra Form JavaScript API” and “W3C Selectors API” (vanilla JavaScript). If necessary, refer to each manual. Please note that the HTML tag <script> for decoration is only available in the Professional Edition. (C) Questetra, Inc. MIT License


Overwrite Frequent Strings using Button Attribute

Input / Output

  • → STRING (STRING_TEXTFIELD): q_Departure (update)
  • → STRING (STRING_TEXTFIELD): q_Arrival (update)

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
Click to overwrite: <br>
<button type='button' onclick='qbpms.form.set( "q_Departure", "NRT" )'>
 NRT: Narita 成田国際空港 (新東京)</button>
<button type='button' onclick='qbpms.form.set( "q_Departure", "HND" )'>
 HND: Haneda 東京国際空港 羽田</button>
<button type='button' onclick='qbpms.form.set( "q_Departure", "KIX" )'>
 KIX: Kansai 関西国際空港 大阪</button><br>
<button type='button' onclick='qbpms.form.set( "q_Departure", "LAX" )'>
 LAX: Los Angeles ロサンゼルス国際空港</button>
<button type='button' onclick='qbpms.form.set( "q_Departure", "ORD" )'>
 ORD: Chicago O'Hare シカゴ・オヘア</button>
<button type='button' onclick='qbpms.form.set( "q_Departure", "ATL" )'>
 ATL: Hartsfield-Jackson Atlanta ハーツフィールド・ジャクソン・アトランタ</button><br>
<button type='button' onclick='qbpms.form.set( "q_Departure", "LHR" )'>
 LHR: Heathrow ロンドン・ヒースロー</button>
<button type='button' onclick='qbpms.form.set( "q_Departure", "FRA" )'>
 FRA: Flughafen Frankfurt フランクフルト</button>
<button type='button' onclick='qbpms.form.set( "q_Departure", "CDG" )'>
 CDG: Aeroport de Paris-Charles-de-Gaulle パリ=シャルル・ド・ゴール</button>
HTML/JavaScript (click to open)
Click to overwrite: <br>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "LAX" )'>
 LAX: Los Angeles ロサンゼルス国際空港</button>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "ORD" )'>
 ORD: Chicago O'Hare シカゴ・オヘア</button>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "ATL" )'>
 ATL: Hartsfield-Jackson Atlanta ハーツフィールド・ジャクソン・アトランタ</button><br>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "LHR" )'>
 LHR: Heathrow ロンドン・ヒースロー</button>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "FRA" )'>
 FRA: Flughafen Frankfurt フランクフルト</button>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "CDG" )'>
 CDG: Aeroport de Paris-Charles-de-Gaulle パリ=シャルル・ド・ゴール</button><br>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "NRT" )'>
 NRT: Narita 成田国際空港 (新東京)</button>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "HND" )'>
 HND: Haneda 東京国際空港 羽田</button>
<button type='button' onclick='qbpms.form.set( "q_Arrival", "KIX" )'>
 KIX: Kansai 関西国際空港 大阪</button>

If to overwrite the process title, write “Title” instead of “q_Departure“.


Overwrite Frequent Strings using Button Content

Overwrite Frequent Strings using Button Content

Input / Output

  • → STRING (STRING_TEXTFIELD): q_AccountTitle (update)

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' class='user_AccountName'>(不明)</button> ((not clear))
<button type='button' class='user_AccountName'>広告宣伝費</button> (Advertising) <br>
<strong>Other Marketing & Admin.</strong>: 
<button type='button' class='user_AccountName'>旅費交通費</button> (Traveling) 
<button type='button' class='user_AccountName'>会議費</button> (Conference) 
<button type='button' class='user_AccountName'>備品・消耗品費</button> (Supplies) 
<button type='button' class='user_AccountName'>租税公課</button> (Taxes&Dues) 
<button type='button' class='user_AccountName'>通信費</button> (Communication)<br> 
<strong>Manufacturing costs</strong>: 
<button type='button' class='user_AccountName'>旅費交通費(製)</button> (Traveling) 
<button type='button' class='user_AccountName'>会議費(製)</button> (Conference) 
<button type='button' class='user_AccountName'>備品・消耗品費(製)</button> (Supplies) 
<button type='button' class='user_AccountName'>租税公課(製)</button> (Taxes&Dues) 
<button type='button' class='user_AccountName'>通信費(製)</button> (Communication) 
<script>
document.querySelectorAll('.user_AccountName').forEach( btn => {
  btn.addEventListener('click', () => {
    const strClicked  = btn.textContent;
    qbpms.form.set( "q_AccountTitle", strClicked );
  });
});
</script>

Append Frequent Strings using Button Attribute

Append Frequent String using Button Attribute

Input / Output

  • ⇆ STRING (STRING_TEXTFIELD): q_Email (append)

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
Click to append:
<button type='button' onclick='user_appendQStr("q_Email", "@questetra.com")'>@questetra.com</button>
<button type='button' onclick='user_appendQStr("q_Email", "@gmail.com")'>@gmail.com</button>
<button type='button' onclick='user_appendQStr("q_Email", "@outlook.com")'>@outlook.com</button>
<script>
function user_appendQStr( qfieldName, strArg ){        // Input Output same field
  let strOriginal = "";
  if( qbpms.form.get( qfieldName ) !== null ){
      strOriginal = qbpms.form.get( qfieldName );
  }
  qbpms.form.set( qfieldName, (strOriginal + strArg) );
}
</script>

Enter Number using NumPad Buttons

Enter Number using NumPad Buttons

Input / Output

  • ⇆ NUMERIC (DECIMAL): q_Cost (update)

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' class='user_numPad' onclick='qbpms.form.set( "q_Cost", "410" )'>410</button>
<button type='button' class='user_numPad' onclick='user_plusQNum( "q_Cost", 80 )'>+80</button><br />

<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "1" )'>1</button>
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "2" )'>2</button>
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "3" )'>3</button><br />
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "4" )'>4</button>
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "5" )'>5</button>
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "6" )'>6</button><br />
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "7" )'>7</button>
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "8" )'>8</button>
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "9" )'>9</button><br />
<button type='button' class='user_numPad' onclick='user_appendQNum( "q_Cost", "0" )'>0</button>
<button type='button' class='user_numPad' onclick='user_timesQNum( "q_Cost", 10  )'>*10</button>
<button type='button' class='user_numPad' onclick='user_timesQNum( "q_Cost", 0.1 )'>/10</button>
<button type='button' class='user_numPad' onclick='user_deletePrevQNum( "q_Cost" )'>BS</button>
<button type='button' class='user_numPad' onclick='user_formatQNum( "q_Cost" )'>Format</button>

<style>
.user_numPad {
  margin: 1px 2px;
  padding: 0 5px;
  border: 1px solid #009900;
  background-color: #ffffff
}
.user_numPad:active{
  background-color: #99ff99
}
</style>

<script>
function user_appendQNum( qfieldName, strArg ){               // Input Output same field
  let strOriginal = "";
  if( qbpms.form.get( qfieldName ) !== null &&                // like as qSTRING
      qbpms.form.get( qfieldName ).toString() !== "0" ){      // no Thousands-separators
      strOriginal = qbpms.form.get( qfieldName ).toString();  // exception: "1.", "1.0" as "1"
  }
  qbpms.form.set( qfieldName, ( strOriginal + strArg ) );
}
function user_deletePrevQNum( qfieldName ){                       // Input Output same field
  if( qbpms.form.get( qfieldName ) !== null ){                    // like as qSTRING
      let strOriginal = qbpms.form.get( qfieldName ).toString();  // no Thousands-separators
      qbpms.form.set( qfieldName, strOriginal.slice( 0, -1 ) );
  }
}
function user_timesQNum( qfieldName, numArg ){
  let numOriginal = new Big(0);
  if( qbpms.form.get( qfieldName ) !== null ){
      numOriginal = qbpms.form.get( qfieldName );
  }
  qbpms.form.set( qfieldName, numOriginal.times( numArg ) );
  // https://mikemcl.github.io/big.js/#times
}
function user_plusQNum( qfieldName, numArg ){
  let numOriginal = new Big(0);
  if( qbpms.form.get( qfieldName ) !== null ){
      numOriginal = qbpms.form.get( qfieldName );
  }
  qbpms.form.set( qfieldName, numOriginal.plus( numArg ) );
  // https://mikemcl.github.io/big.js/#plus
}
function user_formatQNum( qfieldName ){
  if( qbpms.form.get( qfieldName ) !== null ){
      qbpms.form.set( qfieldName, qbpms.form.get( qfieldName ) );
  }
}
</script>

Confirm Selected Value

Confirm Selected Value

Input / Output

  • ← SELECT (SELECT_SINGLE): q_Currency
  • span#user_notice01

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' onclick='qbpms.form.set( "q_Currency", null )'>Clear</button>
<span id="user_notice01">(default)</span>

<script>
qbpms.form.on( 'change', 'q_Currency', user_updateNotice01 );

function user_updateNotice01( eventField ){
  let arrOptions = eventField.value; // = qbpms.form.get( 'q_Currency' );
  console.log( "Number of Selected: " + arrOptions.length );

  let strInnerHtml = '';
  if( arrOptions.length ){
    if( arrOptions[0].value === 'jpy' ){
      strInnerHtml =
        '<span style="font-weight:bold; color:#C60F38"> Selected: ¥</span> ' +
        '(' + arrOptions[0].value + ')'; 
    }else if( arrOptions[0].value === 'usd' ){
      strInnerHtml =
        '<span style="font-weight:bold; color:#1E7E4B"> Selected: $</span> ' +
        '(' + arrOptions[0].value + ')'; 
    }else if( arrOptions[0].value === 'eur' ){
      strInnerHtml =
        '<span style="font-weight:bold; color:#003295"> Selected: €</span> ' +
        '(' + arrOptions[0].value + ')'; 
    }
  }
  document.querySelector( "#user_notice01" ).innerHTML = strInnerHtml;
}
</script>

Check All Checkboxes

Check All Checkboxes

Input / Output

  • → SELECT (SELECT_CHECKBOX): q_Regions

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<div style="text-align: right">
<button type='button' onclick='user_checkCheckboxes(
                   "q_Regions",
                   "CA", "FR", "DE", "IT", "JP", "GB", "US"
                 )'>Check All</button>
<button type='button' onclick='qbpms.form.set( "q_Regions", null )'>Clear All</button>
</div>

<script>
function user_checkCheckboxes( qfieldName, ...arrStrValues ){ // SELECT_CHECKBOX
  // rest parameter syntax allows an indefinite number of arguments as an array
  qbpms.form.set( qfieldName, arrStrValues );
}
</script>

Increment Date using +/- Button

Increment Date using +/- Button

Input / Output

  • ⇆ DATE (DATE_YMD): q_DueDate
  • span#user_footnote_DueDate

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' onclick='
  user_addYmdQDate( "q_DueDate", 0, 0, -7 );
  user_setFootnote_DueDate();
'>-7d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_DueDate", 0, 0, -1 );
  user_setFootnote_DueDate();
'>-1d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_DueDate", 0, 0, 1 );
  user_setFootnote_DueDate();
'>+1d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_DueDate", 0, 0, 7 );
  user_setFootnote_DueDate();
'>+7d</button>
<span id="user_footnote_DueDate"></span>

<script>
qbpms.form.on( 'ready',               user_setFootnote_DueDate ); // for Initial Value
qbpms.form.on( 'change', 'q_DueDate', user_setFootnote_DueDate );

function user_setFootnote_DueDate(){
  let dateTarget = qbpms.form.get( "q_DueDate" );
  if( dateTarget === null ){ return; }
  let dateNow = new Date();
  let bigDateDifMsec  = new Big( dateTarget.getTime() - dateNow.getTime() );
  let bigDateDifHours = bigDateDifMsec.div(1000).div(60).div(60).round(1, Big.roundDown);
  let bigDateDifDays  = bigDateDifMsec.div(1000).div(60).div(60).div(24).round(1, Big.roundDown);
  // https://mikemcl.github.io/big.js/#round
  let strInnerHtml = "";
  if( bigDateDifDays.gt(1) ){
    strInnerHtml =
      '<span style="background-color:#00ffff">' +
      bigDateDifDays.toString() + ' days' +
      '</span>';
  }else if( Big(1).gte(bigDateDifDays) && bigDateDifDays.gt(0) ){
    strInnerHtml =
      '<span style="background-color:#80ff00">' +
      bigDateDifHours.toString() + ' h (tomorrow)' +
      '</span>';
  }else if( Big(0).gte(bigDateDifDays) && bigDateDifDays.gt(-1) ){
    strInnerHtml =
      '<span style="background-color:#ffff00">' +
      bigDateDifHours.abs().toString() + ' h ago (today)' +
      '</span>';
  }else if( Big(-1).gte(bigDateDifDays) ){
    strInnerHtml =
      '<span style="background-color:#ff8000">' +
      bigDateDifDays.abs().toString() + ' days ago (PastDate/過去)' +
      '</span>';
  }
  document.querySelector( '#user_footnote_DueDate' ).innerHTML = strInnerHtml;
}

function user_addYmdQDate( qfieldName, numY, numM, numD, numHour = 0, numMin = 0 ){
  // Supports QDate and QDatetime (Undefined parts will be completed 2000-01-01T00:00:00.)
  let dateTmp = qbpms.form.get( qfieldName );
  if( dateTmp === null ){ return; }
  // console.log( dateTmp.toISOString() );
  dateTmp.setFullYear( dateTmp.getFullYear() + numY );
  dateTmp.setMonth   ( dateTmp.getMonth()    + numM );
  dateTmp.setDate    ( dateTmp.getDate()     + numD );
  dateTmp.setHours   ( dateTmp.getHours()    + numHour );
  dateTmp.setMinutes ( dateTmp.getMinutes()  + numMin );
  qbpms.form.set( qfieldName, dateTmp );
}
</script>

Set Date Range and Datetime

Set Date Range and Datetime

Input / Output

  • ⇆ DATE (DATE_YMD): q_StartDate
  • span#user_footnote_StartDate
  • ⇆ DATE (DATE_YMD): q_EndDate
  • span#user_footnote_EndDate
  • ⇆ DATETIME: q_NotificationDatetime
  • span#user_footnote_NotificationDatetime

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<span id="user_footnote_StartDate"></span><br>
<button type='button' onclick='
  user_rewriteQDate_First( "q_StartDate" );
  user_setFootnote_StartDate();
'>First Day</button>
<button type='button' onclick='
  user_rewriteQDate_MonOfNext( "q_StartDate" );
  user_setFootnote_StartDate();
'>Monday of Next Week</button>
<button type='button' onclick='
  user_rewriteQDate_FirstOfNext( "q_StartDate" );
  user_setFootnote_StartDate();
'>First Day of Next Month</button>
<br />
<button type='button' onclick='
  user_addYmdQDate( "q_StartDate", 0, 0, -1 );
  user_setFootnote_StartDate();
'>-1d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_StartDate", 0, 0, 1 );
  user_setFootnote_StartDate();
'>+1d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_StartDate", 0, 0, 7 );
  user_setFootnote_StartDate();
'>+7d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_StartDate", 0, 1, 0 );
  user_setFootnote_StartDate();
'>+1m</button>
<button type='button' onclick='
  user_addYmdQDate( "q_StartDate", 1, 0, 0 );
  user_setFootnote_StartDate();
'>+1y</button>

<script>
qbpms.form.on( 'ready',                 user_setFootnote_StartDate ); // for Initial Value
qbpms.form.on( 'change', 'q_StartDate', user_setFootnote_StartDate );

function user_setFootnote_StartDate(){
  let dateTarget = qbpms.form.get( "q_StartDate" );
  if( dateTarget === null ){ return; }
  let strInnerHtml = "";
  if( dateTarget.getDay() === 0 ){ // Sunday - Saturday : 0 - 6
    strInnerHtml = '<span style="background-color:#FFCCCC">Sun 日曜日</span>';
  }else if( dateTarget.getDay() === 1 ){
    strInnerHtml = '<span>Mon 月曜日</span>';
  }else if( dateTarget.getDay() === 2 ){
    strInnerHtml = '<span>Tue 火曜日</span>';
  }else if( dateTarget.getDay() === 3 ){
    strInnerHtml = '<span>Wed 水曜日</span>';
  }else if( dateTarget.getDay() === 4 ){
    strInnerHtml = '<span>Thu 木曜日</span>';
  }else if( dateTarget.getDay() === 5 ){
    strInnerHtml = '<span>Fri 金曜日</span>';
  }else if( dateTarget.getDay() === 6 ){
    strInnerHtml = '<span style="background-color:#BDD7EE">Sat 土曜日</span>';
  }
  document.querySelector( '#user_footnote_StartDate' ).innerHTML = strInnerHtml;
}

function user_addYmdQDate( qfieldName, numY, numM, numD, numHour = 0, numMin = 0 ){
  // Supports QDate and QDatetime (Undefined parts will be completed 2000-01-01T00:00:00.)
  let dateTmp = qbpms.form.get( qfieldName );
  if( dateTmp === null ){ return; }
  // console.log( dateTmp.toISOString() );
  dateTmp.setFullYear( dateTmp.getFullYear() + numY );
  dateTmp.setMonth   ( dateTmp.getMonth()    + numM );
  dateTmp.setDate    ( dateTmp.getDate()     + numD );
  dateTmp.setHours   ( dateTmp.getHours()    + numHour );
  dateTmp.setMinutes ( dateTmp.getMinutes()  + numMin );
  qbpms.form.set( qfieldName, dateTmp );
}

function user_rewriteQDate_First( qfieldName ){
  let dateTmp = qbpms.form.get( qfieldName );
  if( dateTmp === null ){ return; }
  dateTmp.setDate(1);
  qbpms.form.set( qfieldName, dateTmp );
}
function user_rewriteQDate_MonOfNext( qfieldName ){
  let dateTmp = qbpms.form.get( qfieldName );
  if( dateTmp === null ){ return; }
  // getDay() -> Sunday - Saturday : 0 - 6
  // plus(7-getDay()) -> Weekend Sunday
  dateTmp.setDate( dateTmp.getDate() + ( 7 - dateTmp.getDay() ) + 1 );
  // Week: Monday - Sunday (ISO 8601)
  qbpms.form.set( qfieldName, dateTmp );
}
function user_rewriteQDate_FirstOfNext( qfieldName ){
  let dateTmp = qbpms.form.get( qfieldName );
  if( dateTmp === null ){ return; }
  dateTmp.setDate(1);
  dateTmp.setMonth( dateTmp.getMonth() + 1 );
  qbpms.form.set( qfieldName, dateTmp );
}
</script>
HTML/JavaScript (click to open)
<span id="user_footnote_EndDate"></span>
<br />
<button type='button' onclick='user_copyPasteQDate( "q_StartDate", "q_EndDate" )'>copy from q_StartDate</button>
<br />
<button type='button' onclick='
  user_addYmdQDate( "q_EndDate", 1, 0, -1 );
  user_setFootnote_EndDate();
'>+1y-1d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_EndDate", 0, 1, -1 );
  user_setFootnote_EndDate();
'>+1m-1d</button>
<br />
<button type='button' onclick='
  user_addYmdQDate( "q_EndDate", 0, -1, 0 );
  user_setFootnote_EndDate();
'>-1m</button>
<button type='button' onclick='
  user_addYmdQDate( "q_EndDate", 0, 0, -1 );
  user_setFootnote_EndDate();
'>-1d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_EndDate", 0, 0, 1 );
  user_setFootnote_EndDate();
'>+1d</button>
<button type='button' onclick='
  user_addYmdQDate( "q_EndDate", 0, 1, 0 );
  user_setFootnote_EndDate();
'>+1m</button>
<button type='button' onclick='
  user_addYmdQDate( "q_EndDate", 1, 0, 0 );
  user_setFootnote_EndDate();
'>+1y</button>

<script>
qbpms.form.on( 'ready',               user_setFootnote_EndDate ); // for Initial Value
qbpms.form.on( 'change', 'q_EndDate', user_setFootnote_EndDate );

function user_setFootnote_EndDate(){
  let dateStart = qbpms.form.get( "q_StartDate" );
  if( dateStart === null ){ return; }
  let dateEnd   = qbpms.form.get( "q_EndDate" );
  if( dateEnd   === null ){ return; }
  let numDateDifMsec  = dateEnd.getTime() - dateStart.getTime();
  let numDateDifDays  = numDateDifMsec /1000 /60 / 60 /24;
  let strInnerHtml = "";
  if( numDateDifDays < 0 ){
    strInnerHtml =
      '<span style="background-color:#ff0000"> Start-End Range: ' +
      ( numDateDifDays + 1 )  + ' days ( < 0 )' +
      '</span>';
  }else{
    strInnerHtml = ' Start-End Range: ' +  ( numDateDifDays + 1 ) + ' days';
  }
  document.querySelector( '#user_footnote_EndDate' ).innerHTML = strInnerHtml;
}

function user_copyPasteQDate( qfieldNameSource, qfieldNameDestination ){
  // Also supports QDatetime (Undefined parts completed 2000-01-01T00:00:00.)
  if( qbpms.form.get( qfieldNameSource ) === null ){ return; }
  const dateSource = qbpms.form.get( qfieldNameSource );
  qbpms.form.set( qfieldNameDestination, dateSource );
}
</script>
HTML/JavaScript (click to open)
<span id="user_footnote_NotificationDatetime"></span>
<br />
<button type='button' onclick='
  user_copyPasteQDate( "q_StartDate", "q_NotificationDatetime" );
  user_setFootnote_NotificationDatetime();
'>copy from q_StartDate</button>
<br />
<button type='button' onclick='
user_setHmQDatetime( "q_NotificationDatetime", 9, 0 );
  user_setFootnote_NotificationDatetime();
'>09:00</button>
<button type='button' onclick='
user_setHmQDatetime( "q_NotificationDatetime", 12, 0 );
  user_setFootnote_NotificationDatetime();
'>12:00</button>
<button type='button' onclick='
user_setHmQDatetime( "q_NotificationDatetime", 17, 0 );
  user_setFootnote_NotificationDatetime();
'>17:00</button>
<br />
<button type='button' onclick='user_addYmdQDate( "q_NotificationDatetime", 0, -1, 0 ); user_setFootnote_NotificationDatetime();'>-1m</button>
<button type='button' onclick='user_addYmdQDate( "q_NotificationDatetime", 0, 0, -1 ); user_setFootnote_NotificationDatetime();'>-1d</button>
<button type='button' onclick='user_addYmdQDate( "q_NotificationDatetime", 0, 0, 0, -1 ); user_setFootnote_NotificationDatetime();'>-1h</button>
<button type='button' onclick='user_addYmdQDate( "q_NotificationDatetime", 0, 0, 0, 1 ); user_setFootnote_NotificationDatetime();'>+1h</button>
<button type='button' onclick='user_addYmdQDate( "q_NotificationDatetime", 0, 0, 1 ); user_setFootnote_NotificationDatetime();'>+1d</button>

<script>
qbpms.form.on( 'ready',                            user_setFootnote_NotificationDatetime ); // for Initial Value
qbpms.form.on( 'change', 'q_NotificationDatetime', user_setFootnote_NotificationDatetime );

function user_setFootnote_NotificationDatetime(){
  let dateStart = qbpms.form.get( "q_StartDate" );
  if( dateStart === null ){ return; }
  let dateNoDa  = qbpms.form.get( "q_NotificationDatetime" );
  if( dateNoDa  === null ){ return; }
  let numDateDifMsec  = dateNoDa.getTime() - dateStart.getTime();
  let numDateDifDays  = numDateDifMsec /1000 /60 / 60 /24;
  let strInnerHtml = "";
  if( numDateDifDays > 0 ){
    strInnerHtml =
      '<span style="background-color:#ff0000"> Start-Notification: ' +
      Math.floor( numDateDifDays )  + ' days passed ( > 0 )' +
      '</span>';
  }else{
    strInnerHtml = ' Start-Notification: ' +  Math.floor( numDateDifDays ) + ' days';
  }
  document.querySelector( '#user_footnote_NotificationDatetime' ).innerHTML = strInnerHtml;
}

function user_setHmQDatetime( qfieldName, numHour = 0, numMin = 0 ){
  let dateTmp = qbpms.form.get( qfieldName );
  if( dateTmp === null ){ return; }
  // console.log( dateTmp.toISOString() );
  dateTmp.setHours   ( numHour );
  dateTmp.setMinutes ( numMin );
  qbpms.form.set( qfieldName, dateTmp );
}
</script>

Copy to Clipboard

Copy to Clipboard

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_SalesTSV
  • → clipboard

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' onclick='
  user_copyQStringToClipboard( "q_SalesTSV", "#user_clicklog_copy" )
'>copy q_SalesTSV to Clipboard</button>
<span id="user_clicklog_copy"></span><br>

<script>
function user_copyQStringToClipboard( qfieldName, spanId ){
  let strValue = qbpms.form.get( qfieldName );
  let strInnerHtml = "";
  if( strValue === "" ){         // ReadWrite
    strInnerHtml = '<span style="color:#ff0000"> empty, clipboard not updated </span>';
  }else if( strValue === null ){ // Read-only
    strInnerHtml = '<span style="color:#ff8000"> empty, clipboard not updated </span>';
  }else{
    navigator.clipboard.writeText( strValue );
    // https://developer.mozilla.org/docs/Web/API/Clipboard_API
    let dateNow = new Date();
    strInnerHtml = dateNow.toLocaleTimeString() + " copied (" + strValue.length + ")";
  }
  document.querySelector( spanId ).innerHTML = strInnerHtml;
}
</script>

Tab codes in Read-only String will not be copied.


Validate with Regular Expressions

Validate with Regular Expressions

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_Email
  • span#user_footnote_Email

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<span id="user_footnote_Email"></span><br>

<script>
qbpms.form.on( 'change', 'q_Email', user_setFootnote_Email );

function user_setFootnote_Email(){
  let regEmail = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
    // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
  let strInnerHtml = "";
  let strTarget = qbpms.form.get( "q_Email" );
  if( strTarget === "" ){          // ReadWrite QString
    strInnerHtml = '<span style="color:#ffa500"> empty </span>';
  }else if( strTarget === null ){  // Read-only QString ( no case )
    strInnerHtml = '<span style="color:#ffa500"> empty / 空です </span>';
  }else{
    if( regEmail.test( strTarget ) ){
      strInnerHtml = '<span style="color:#0000ff"> ✓ valid / 有効です </span>';
    }else{
      strInnerHtml = '<span style="color:#ff0000"> ✗ invalid / 問題があります </span>';
    }
  }
  document.querySelector( '#user_footnote_Email' ).innerHTML = strInnerHtml;
}
</script>

Generate QR Code

Generate QR Code image in JavaScript

Input / Output

  • ← STRING (STRING_TEXTAREA) q_Url
  • span#user_output_encode

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' onclick='
  user_encodeQStringToQrcode( "q_Url", "#user_output_encode" )
'>encode q_Url to QR-code image</button><br>
<span id="user_output_encode"></span>

<script>
function user_encodeQStringToQrcode( qfieldName, spanId ){
  let strValue = qbpms.form.get( qfieldName );
  console.log( "Encode QString: " + strValue );
  let strInnerHtml = "";

  if( strValue === "" ){   // ReadWrite
    strInnerHtml = '<span style="color:#ff0000"> empty, QR-Code image not created </span>';
    document.querySelector( spanId ).innerHTML = strInnerHtml;
    return;
  }
  if( strValue === null ){ // Read-only
    strInnerHtml = '<span style="color:#ff8000"> empty, QR-Code image not created </span>';
    document.querySelector( spanId ).innerHTML = strInnerHtml;
    return;
  }

  // js-dynamic-loading
  user_getScript( 'https://cdn.jsdelivr.net/npm/qrcode@1.4.4/build/qrcode.min.js',
                  'sha256-DhdpoP64xch/Frz8CiBQE12en55NX+RhlPGRg6KWm5s=',
                  function () {

    // https://www.jsdelivr.com/package/npm/qrcode?version=1.4.4
    // https://cdn.jsdelivr.net/npm/qrcode@1.4.4/license
    // https://cdn.jsdelivr.net/npm/qrcode@1.4.4/README.md
    const opts = {
      errorCorrectionLevel: 'H', // L, M, Q, H
      type: 'image/png', width: 200, quality: 0.3, margin: 1,
      color: { dark:"#000", light:"#FFF0" } // alpha channel (opacity) = 0
    };
    QRCode.toDataURL( strValue, opts, function (err, url) {
      if (err) throw err;
      strInnerHtml = '';
      strInnerHtml += '<a href="' + url + '" download="QR.png">';
      strInnerHtml += '<img src="' + url + '" alt="QR Code" />';
      strInnerHtml += '<br>';
      strInnerHtml += 'Click to Download';
      strInnerHtml += '</a>';
      document.querySelector( spanId ).innerHTML = strInnerHtml;
    });

  }); // endof js-dynamic-loading
}

function user_getScript(scriptUrl, integrity, callback) { // for dynamic-loading
  const script = document.createElement('script');
  script.src = scriptUrl;
  // Subresource Integrity (SRI) is a security feature that enables browsers to verify that
  // resources they fetch (for example, from a CDN) are delivered without unexpected manipulation.
  // https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
  script.integrity = integrity;
  script.crossOrigin = "anonymous"; // note: property name is "crossOrigin", not "crossorigin"
  script.onload = callback;
  document.body.appendChild(script);
}
</script>

Preview HTML Code in New Window

Preview HTML Code in New Window

Input / Output

  • ← STRING (STRING_TEXTAREA): q_Str_EmailHtmlReport
  • → window: _Blank

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' onclick='
  user_previewHtmlInNewWindow( "q_Str_EmailHtmlReport" )
'>Open Window to Preview HTML</button>

<script>
function user_previewHtmlInNewWindow( qfieldName ) {
  let strHtml = qbpms.form.get( qfieldName );
  if( strHtml === null ){ return; } // when ReadOnly
  if( strHtml === "" ){
    throw new Error('No String in this Textarea');
  }
  let blob = new Blob([ strHtml ], { type: 'text/html' });
  let strUrl = URL.createObjectURL(blob);
  window.open( strUrl, "_Blank" );
}
</script>

Export Text as UTF8B-File

Input / Output

  • ← STRING (STRING_TEXTAREA): q_MultilineString
  • download.txt (download)

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type='button' onclick='user_exportAsFile_MultilineString()'>Save as Text File</button>
<script>
function user_exportAsFile_MultilineString(){
  let strTmp = qbpms.form.get("q_Multiline_String");
  if( strTmp === null ){ return; } // when ReadOnly
  if( strTmp === "" )  { return; } // when ReadWrite
  let bom  = new Uint8Array([0xEF, 0xBB, 0xBF]);
  let blob = new Blob([bom, strTmp], {type: 'text/plain'});
  let url = (window.URL || window.webkitURL).createObjectURL(blob);
  let link = document.createElement('a');
  link.download = 'download.txt';
  link.href = url;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}
</script>

Check Form History via REST API

Check Form History via REST API

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_ContentsOfDuties
  • ← Questetra Workflow API, ProcessInstance: get /API/OR/ProcessInstance/list
  • span#user_Result_ContentsOfDuties
    • button.user_Appender
    • → STRING (STRING_TEXTFIELD): q_ContentsOfDuties

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<button type="button" onclick="user_getHistory_ContentsOfDuties()" class="user_Search">Sample Search →</button>
<span id="user_footnote_ContentsOfDuties"></span>

<script>
document.addEventListener("click", function (e) {
  console.log( " DocumentElement clicked: " + e.target.textContent );
  if (e.target.classList.contains("user_Appender")) {
    qbpms.form.set( "q_ContentsOfDuties", e.target.value );        ///EDIT///
  }
});
function user_getHistory_ContentsOfDuties() {
  let numTARGETID      = 8;                                        ///EDIT/// ID of q_ContentsOfDuties
  let numLIMITSIZE     = 10;                                       ///EDIT///
  let numWORKFLOWAPPID = 1883;                                     ///EDIT///
  let strSearchWord    = qbpms.form.get( "q_ContentsOfDuties" );                     ///EDIT///
  let elResultSpan     = document.querySelector("#user_footnote_ContentsOfDuties");  ///EDIT///

  let objCriteria = {}; // JSON criteria: (BPMS v13.3~)
      // https://questetra.zendesk.com/hc/ja/articles/4415341995289
      // https://questetra.zendesk.com/hc/en-us/articles/4415341995289
      objCriteria.processModelInfoId = numWORKFLOWAPPID;
      objCriteria.processInstanceState = [];
      objCriteria.processInstanceState[0] = "ENDED"; // only completed instances
      if ( strSearchWord !== "" ) {
        objCriteria.data = [];
        objCriteria.data[0] = {};
        objCriteria.data[0].type   = "string";
        objCriteria.data[0].number = numTARGETID;
        objCriteria.data[0].method = "contains";
        objCriteria.data[0].value  = strSearchWord;
      }
      objCriteria.fields = [];
      objCriteria.fields[0] = {};
      objCriteria.fields[0].type   = "string";
      objCriteria.fields[0].number = numTARGETID;
  let xhr = new XMLHttpRequest();
      xhr.open( "GET", "/API/OR/ProcessInstance/list?limit=" + numLIMITSIZE +
                       "&criteria=" + encodeURIComponent(JSON.stringify(objCriteria)) );
      xhr.responseType = 'json';
      xhr.send();
      xhr.onload = function() {
        if (xhr.status != 200) {
          console.error(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. "404: Not Found"
        } else { // show the result
          let objResponse = xhr.response;
          console.log( " Decoration XHR: Found PastData " + objResponse.processInstances.length + 
                       "/" + objResponse.count  );
          elResultSpan.innerHTML = ""; // clear
          for (let i = 0; i < objResponse.processInstances.length; i++ ) {
            let elNewAnchor = document.createElement("a");
                elNewAnchor.href      = "/OR/ProcessInstance/view?processInstanceId=" + 
                                        objResponse.processInstances[i].processInstanceId;
                elNewAnchor.target    = "_blank";
                elNewAnchor.innerText = "p" + objResponse.processInstances[i].processInstanceId;
            let elNewButton = document.createElement("button");
                elNewButton.type = "button";
                elNewButton.classList.add("user_Appender");
                if ( objResponse.processInstances[i].data[numTARGETID].value === null ){
                  elNewButton.value     = "";
                  elNewButton.innerHTML = "?";
                }else{
                  elNewButton.value     = objResponse.processInstances[i].data[numTARGETID].value;
                  elNewButton.innerText = objResponse.processInstances[i].data[numTARGETID].value;
                }
            // "<a>p123</a>=<button>消耗品費</button>"
            elResultSpan.append( " ", elNewAnchor,"=" );
            elResultSpan.append( elNewButton );
          }
        }
        console.log( " Decoration XHR: finished " );
      };
      xhr.onerror = function() {
        alert("Request failed");
      };
}
</script>

<style type="text/css">
button.user_Appender {
    border          : 0;
    line-height     : 1.2;
    padding         : 3px 8px;
    font-size       : 0.9rem;
    text-align      : center;
    color           : #fff;
    text-shadow     : 1px 1px 1px #000;
    border-radius   : 10px;
    background-color: #050505;
}
button.user_Appender:hover {
    background-color: #00bb00;
}
button.user_Appender:active {
    box-shadow      : inset -2px -2px 3px rgba(255, 255, 255, .6),
                      inset 2px 2px 3px rgba(0, 0, 0, .6);
}

button.user_Search {
  display       : inline-block;
  border-radius : 5%;
  font-size     : 1rem;
  text-align    : center;
  cursor        : pointer;
  padding       : 5px 20px;
  background    : #009900;
  color         : #ffffff;
  line-height   : 1em;
  transition    : .3s;
  box-shadow    : 2px 2px 3px #666666;
  border        : 2px solid #009900;
}
button.user_Search:hover {
  box-shadow    : none;
  color         : #009900;
  background    : #ffffff;
}
</style>

Switch between Edit and Read-Only

Switch between Edit and Read-Only

Input / Output

  • ← SELECT (SELECT_SINGLE) q_Answer
  • → STRING (STRING_TEXTFIELD) q_FreeText

Code Example for DESCRIPTION of Workflow-App

HTML/JavaScript (click to open)
<script>
qbpms.form.on( 'ready',              user_syncWithSelected ); // for Initial Value
qbpms.form.on( 'change', 'q_Answer', user_syncWithSelected );

function user_syncWithSelected(){
  let qfieldNameSelect = "q_Answer";      //// EDIT
  let qfieldNameString = "q_FreeText";    //// EDIT

  /// Format as of v14.1 (Risk of change in the future)
  let selector    = "div[data-var-name='" + qfieldNameString + "']";
  let elContainer = document.querySelector( selector );
  let elTarget    = elContainer.querySelector( "input" );

  let arrOptions = qbpms.form.get( qfieldNameSelect );
  console.log( "Num of Selected: " + arrOptions.length );
  if( arrOptions.length ){
    if( arrOptions[0].value === 'other' ){
      // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly
      // https://developer.mozilla.org/ja/docs/Web/HTML/Attributes/readonly
      elTarget.readOnly = false;
      elTarget.style.backgroundColor = "#ffffff";
      return;
    }
  }
  elTarget.readOnly = true;
  elTarget.style.backgroundColor = "#f2f2f2";
}
</script>

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: