Task Form Decoration

Latest “Task Form Decorations”: https://support.questetra.com/category/deco/
最新の”処理画面デコレーション”: https://support.questetra.com/ja/category/deco-ja/

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

The UI/UX design of the Task form determines the productivity of each process step. In particular, the “time required” and “error rate” for unskilled workers depend greatly on UI/UX design. It is important to keep improving task forms from the viewpoint of on-site workers. The following form decoration examples (HTML JavaScript code) can be freely duplicated and used free of charge.

タスク処理画面(タスクForm)のUI/UXデザインは、各工程の生産性を左右します。特に未習熟者の「所要時間」や「ミス率」は、UI/UXデザインに大きく依存します。現場処理者の視点に立って、処理画面(タスクフォーム)の改善サイクルを回し続けることが重要です。以下のFormデコレーション例(HTML JavaScript コード)は、無料で自由に複製利用して頂けます。

The workflow app administrator sets the decorations in the properties of each data item. Knowledge of HTML/JavaScript is required. / ワークフローアプリの管理者は、各データ項目のプロパティ部分にて説明デコレーションを設定します。HTML/JavaScript の知識が必要です。

Data modeling - Questetra BPM Suite
Data modeling – Questetra BPM Suite

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


Generate Password with Click

"Hello World" for learning 'Questetra Form JavaScript API'. Add this code to the Description area of the string data item where you want the "Generate Password" button to appear.
これは「Questetra Form JavaScript API」を学習するための「Hello World」です。このコードを "パスワード生成" ボタンを表示させたい項目の[説明]部に設定します。

Online DEMO (public form)

Input / Output

  • → STRING (STRING_TEXTFIELD) q_NewPassword (update)
HTML/JavaScript (click to open)
<script>
/*
== Generate Password with Click ==
    "Hello World" for learning 'Questetra Form JavaScript API'.
    Add this code to the Description area of the string data item
    where you want the "Generate Password" button to appear.

    これは「Questetra Form JavaScript API」を学習するための「Hello World」です。
    このコードを "パスワード生成" ボタンを表示させたい項目の[説明]部に設定します。
*/

function user_overwriteRandomPasswordToQstr ( qStrFieldName ) {
  const strNumeric = "23456789";        const numNumeric = 2;
  const strUpper   = "ACFGHJKNRSUXYZ";  const numUpper   = 4;
  const strLower   = "acfghjknrsuxyz";  const numLower   = 4;
  const strSymbol  = "#$%&+-";          const numSymbol  = 2;

  let strPwd = "";
  for( let i = 0; i < numNumeric; i++ ){
    strPwd  += strNumeric.charAt( Math.floor( Math.random() * strNumeric.length ) );
  }
  for( let i = 0; i < numUpper; i++ ){
    strPwd  += strUpper.charAt(   Math.floor( Math.random() * strUpper.length ) );
  }
  for( let i = 0; i < numLower; i++ ){
    strPwd  += strLower.charAt(   Math.floor( Math.random() * strLower.length ) );
  }
  for( let i = 0; i < numSymbol; i++ ){
    strPwd  += strSymbol.charAt(  Math.floor( Math.random() * strSymbol.length ) );
  }

  let arrPwd = strPwd.split("");
  let j = arrPwd.length;
  while ( j > 1 ) {  // shuffle (Fisher-Yates)
    let k = Math.floor( Math.random() * j );
    j = j - 1;
    if (j === k) continue;
    let charTmp = arrPwd[j];
    arrPwd[j]   = arrPwd[k];
    arrPwd[k]   = charTmp;
  }

  qbpms.form.set( qStrFieldName, arrPwd.join("") ); // update the input form of Questetra
}
</script>

<button type="button" 
 onclick="user_overwriteRandomPasswordToQstr (
            'q_NewPassword'
          )"> Generate Password </button>
 ※ Click to Overwrite / クリックで上書き

Overwrite Frequent Strings using Button Attribute

Input / Output

  • → STRING (STRING_TEXTFIELD): q_Departure (update)
  • → STRING (STRING_TEXTFIELD): q_Arrival (update)
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“. / プロセス件名を上書きしたい場合、 “q_Departure” の代わりに “Title” と書きます。


Overwrite Frequent Strings using Button Content

Overwrite Frequent Strings using Button Content

Input / Output

  • → STRING (STRING_TEXTFIELD): q_AccountTitle (update)
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>

Overwrite Multiline String with Click

Overwrite Multiline String with Click - Questetra Form JavaScript API

Online DEMO (public form)

Input / Output

  • → STRING (STRING_TEXTAREA) q_EmailBody (update)
HTML/JavaScript (click to open)
<button type='button' onclick='qbpms.form.set( "q_EmailBody",
`Dear Questetra BPM Suite Administrator,

Sincerely,
The Questetra BPM Suite Operations Team
https://support.questetra.com/`
)'>English Template</button>


<button type='button' onclick='qbpms.form.set( "q_EmailBody",
`Questetra BPM Suite システム管理者様

--
株式会社クエステトラ / Questetra, Inc.
https://support.questetra.com/ja/`
)'>日本語テンプレ</button>


 ※ Click to Overwrite / クリックで上書き

Append Frequent Strings using Button Attribute

Append Frequent String using Button Attribute

Input / Output

  • ⇆ STRING (STRING_TEXTFIELD): q_Email (append)
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>

Convert to Katakana

Convert to Katakana in javascript (カタカナ変換)
全角ひらがなから全角カタカナへ
半角カタカナから全角カタカナへ

Online DEMO (public form)

Input / Output

  • ⇆ STRING (STRING_TEXTFIELD): q_SurnameKatakana
HTML/JavaScript (click to open)
<button onclick='user_qStrStrCopyWithConvertHiraKata( "q_SurnameKatakana", "q_SurnameKatakana" );'
 type='button'>かな⇒カナ</button>
<button onclick='user_qStrStrCopyWithConvertHalfFull( "q_SurnameKatakana", "q_SurnameKatakana" );'
 type='button'>半カナ⇒カナ</button>
<button onclick='user_qStrStrCopyWithConvertKataHira( "q_SurnameKatakana", "q_SurnameKatakana" );'
 type='button'>(カナ⇒かな)</button>
<button onclick='user_qStrStrCopyWithConvertFullHalf( "q_SurnameKatakana", "q_SurnameKatakana" );'
 type='button'>(カナ⇒半カナ)</button>

<script>
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+ひらカタ変換) ▼▼▼
function user_qStrStrCopyWithConvertHiraKata( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceHiragana2Katakana( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+カタひら変換) ▼▼▼
function user_qStrStrCopyWithConvertKataHira( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceKatakana2Hiragana( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+全角変換) ▼▼▼
function user_qStrStrCopyWithConvertHalfFull( qStrFieldInput, qStrFieldOutput ){
  let strInput = qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceHalf2Full( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+半角変換) ▼▼▼
function user_qStrStrCopyWithConvertFullHalf( qStrFieldInput, qStrFieldOutput ){
  let strInput = qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceFull2Half( strInput ) );
}


//▼ カナ変換(ひらがな から カタカナ への変換) ▼
function user_strReplaceHiragana2Katakana( str ){
  return str.replace( /[\u3041-\u3096]/g, function(s){
    return String.fromCharCode( s.charCodeAt(0) + 0x60 );
  });
}
//▼ ひら変換(カタカナ から ひらがな への変換) ▼
function user_strReplaceKatakana2Hiragana( str ){
  return str.replace( /[\u30a1-\u30f6]/g, function(s){
    return String.fromCharCode( s.charCodeAt(0) - 0x60 );
  });
}
//▼ 半角(カタカナ) から 全角(カタカナ) への変換 ▼
function user_strReplaceHalf2Full( str ){
  const objHalf2Full = {
    'ガ': 'ガ', 'ギ': 'ギ', 'グ': 'グ', 'ゲ': 'ゲ', 'ゴ': 'ゴ',
    'ザ': 'ザ', 'ジ': 'ジ', 'ズ': 'ズ', 'ゼ': 'ゼ', 'ゾ': 'ゾ',
    'ダ': 'ダ', 'ヂ': 'ヂ', 'ヅ': 'ヅ', 'デ': 'デ', 'ド': 'ド',
    'バ': 'バ', 'ビ': 'ビ', 'ブ': 'ブ', 'ベ': 'ベ', 'ボ': 'ボ',
    'パ': 'パ', 'ピ': 'ピ', 'プ': 'プ', 'ペ': 'ペ', 'ポ': 'ポ', 'ヴ': 'ヴ',
    'ア': 'ア', 'イ': 'イ', 'ウ': 'ウ', 'エ': 'エ', 'オ': 'オ',
    'カ': 'カ', 'キ': 'キ', 'ク': 'ク', 'ケ': 'ケ', 'コ': 'コ',
    'サ': 'サ', 'シ': 'シ', 'ス': 'ス', 'セ': 'セ', 'ソ': 'ソ',
    'タ': 'タ', 'チ': 'チ', 'ツ': 'ツ', 'テ': 'テ', 'ト': 'ト',
    'ナ': 'ナ', 'ニ': 'ニ', 'ヌ': 'ヌ', 'ネ': 'ネ', 'ノ': 'ノ',
    'ハ': 'ハ', 'ヒ': 'ヒ', 'フ': 'フ', 'ヘ': 'ヘ', 'ホ': 'ホ',
    'マ': 'マ', 'ミ': 'ミ', 'ム': 'ム', 'メ': 'メ', 'モ': 'モ',
    'ヤ': 'ヤ', 'ユ': 'ユ', 'ヨ': 'ヨ',
    'ラ': 'ラ', 'リ': 'リ', 'ル': 'ル', 'レ': 'レ', 'ロ': 'ロ',
    'ワ': 'ワ', 'ヲ': 'ヲ', 'ン': 'ン',
    'ァ': 'ァ', 'ィ': 'ィ', 'ゥ': 'ゥ', 'ェ': 'ェ', 'ォ': 'ォ',
    'ッ': 'ッ', 'ャ': 'ャ', 'ュ': 'ュ', 'ョ': 'ョ',
    '。': '。', '、': '、', 'ー': 'ー', '「': '「', '」': '」', '・': '・'
  };
  // 業務によっては「ハイフン("-")を長音("ー")に置換」すべき場合も。
  // 業務によっては「ヷ (va)」や「ヺ (vo)」等を追加すべき場合も。

  const regHalf = new RegExp( '(' + Object.keys( objHalf2Full ).join('|') + ')', 'g' );
    // array of object's own enumerable property names
    // new RegExp( (ガ|ギ|グ|…|・), 'g' );
  return str.replace( regHalf, function(s){ return objHalf2Full[s]; } )
            .replace(/゙/g, '゛')
            .replace(/゚/g, '゜');
}
//▼ 全角(カタカナ) から 半角(カタカナ) への変換 ▼
function user_strReplaceFull2Half( str ){
  const objFull2Half = {
    "ガ": "ガ", "ギ": "ギ", "グ": "グ", "ゲ": "ゲ", "ゴ": "ゴ",
    "ザ": "ザ", "ジ": "ジ", "ズ": "ズ", "ゼ": "ゼ", "ゾ": "ゾ",
    "ダ": "ダ", "ヂ": "ヂ", "ヅ": "ヅ", "デ": "デ", "ド": "ド",
    "バ": "バ", "ビ": "ビ", "ブ": "ブ", "ベ": "ベ", "ボ": "ボ",
    "パ": "パ", "ピ": "ピ", "プ": "プ", "ペ": "ペ", "ポ": "ポ", "ヴ": "ヴ",
    "ア": "ア", "イ": "イ", "ウ": "ウ", "エ": "エ", "オ": "オ",
    "カ": "カ", "キ": "キ", "ク": "ク", "ケ": "ケ", "コ": "コ",
    "サ": "サ", "シ": "シ", "ス": "ス", "セ": "セ", "ソ": "ソ",
    "タ": "タ", "チ": "チ", "ツ": "ツ", "テ": "テ", "ト": "ト",
    "ナ": "ナ", "ニ": "ニ", "ヌ": "ヌ", "ネ": "ネ", "ノ": "ノ",
    "ハ": "ハ", "ヒ": "ヒ", "フ": "フ", "ヘ": "ヘ", "ホ": "ホ",
    "マ": "マ", "ミ": "ミ", "ム": "ム", "メ": "メ", "モ": "モ",
    "ヤ": "ヤ", "ユ": "ユ", "ヨ": "ヨ",
    "ラ": "ラ", "リ": "リ", "ル": "ル", "レ": "レ", "ロ": "ロ",
    "ワ": "ワ", "ヲ": "ヲ", "ン": "ン",
    "ァ": "ァ", "ィ": "ィ", "ゥ": "ゥ", "ェ": "ェ", "ォ": "ォ",
    "ッ": "ッ", "ャ": "ャ", "ュ": "ュ", "ョ": "ョ",
    "。": "。", "、": "、", "ー": "ー", "「": "「", "」": "」", "・": "・"
  };
  const regFull = new RegExp( '(' + Object.keys( objFull2Half ).join('|') + ')', 'g' );
  return str.replace( regFull, function(s){ return objFull2Half[s]; })
            .replace(/゛/g, '゙')
            .replace(/゜/g, '゚');
}
</script>

Input / Output

  • ⇆ STRING (STRING_TEXTFIELD): q_GivennameKatakana
HTML/JavaScript (click to open)
<button onclick='user_qStrStrCopyWithConvertHiraKata( "q_GivennameKatakana", "q_GivennameKatakana" );'
 type='button'>かな⇒カナ</button>
<button onclick='user_qStrStrCopyWithConvertHalfFull( "q_GivennameKatakana", "q_GivennameKatakana" );'
 type='button'>半カナ⇒カナ</button>
<button onclick='user_qStrStrCopyWithConvertKataHira( "q_GivennameKatakana", "q_GivennameKatakana" );'
 type='button'>(カナ⇒かな)</button>
<button onclick='user_qStrStrCopyWithConvertFullHalf( "q_GivennameKatakana", "q_GivennameKatakana" );'
 type='button'>(カナ⇒半カナ)</button>
Unicode for Japanese Hiragana Katakana
Hepburn-romanization Katakana

Convert to Roman Letters

Convert to Roman Letters in JavaScript (ローマ字変換)
全角カタカナからヘボン式ローマ字

Online DEMO (public form)

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_SurnameKatakana
  • → STRING (STRING_TEXTFIELD): q_SurnameRomanized
    • ← STRING (STRING_TEXTFIELD): q_SurnameRomanized
    • → STRING (STRING_TEXTFIELD): q_EmailAddr
HTML/JavaScript (click to open)
<button onclick='user_qStrStrCopyWithConvertKataRoma (  "q_SurnameKatakana", "q_SurnameRomanized" );'
 type='button'>カナ⇒romaji</button>
<button onclick='user_qStrStrCopyWithCapitalizeAll   ( "q_SurnameRomanized", "q_SurnameRomanized" );'
 type='button'>romaji⇒Romaji</button>
<button onclick='user_qStrStrCopyWithConvertUpperCase( "q_SurnameRomanized", "q_SurnameRomanized" );'
 type='button'>Romaji⇒ROMAJI</button>
<button onclick='user_qStrStrCopyWithConvertLowerCase( "q_SurnameRomanized", "q_SurnameRomanized" );'
 type='button'>(Romaji⇒romaji)</button>
<br>
※長音の削除:
<button onclick='user_qStrStrCopyWithConvertUU2U( "q_SurnameRomanized", "q_SurnameRomanized" );'
 type='button'>"UU"⇒"U"</button>
<button onclick='user_qStrStrCopyWithConvertOU2O( "q_SurnameRomanized", "q_SurnameRomanized" );'
 type='button'>"OU"⇒"O"</button>
<button onclick='user_qStrStrCopyWithConvertOO2O( "q_SurnameRomanized", "q_SurnameRomanized" );'
 type='button'>"OO"⇒"O"</button>
<button onclick='user_qStrStrCopyWithConvertO2OH( "q_SurnameRomanized", "q_SurnameRomanized" );'
 type='button'>("O"⇒"OH")</button>
<br>
<button onclick='user_qStrStrCopyAsEmail( "q_SurnameRomanized", "example.com", "q_EmailAddr" );'
 type='button'>+"@example.com" ⇒ ▼Email▼</button>

<script>
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+ローマ字へ変換) ▼▼▼
function user_qStrStrCopyWithConvertKataRoma( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceKatakana2Romaji( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+大文字化) ▼▼▼
function user_qStrStrCopyWithConvertUpperCase( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceUpperCase( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+小文字化) ▼▼▼
function user_qStrStrCopyWithConvertLowerCase( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceLowerCase( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+大文字始まり化) ▼▼▼
function user_qStrStrCopyWithCapitalizeAll( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strCapitalizeAll( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+UU2U変換) ▼▼▼
function user_qStrStrCopyWithConvertUU2U( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceUU2U( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+OU2O変換) ▼▼▼
function user_qStrStrCopyWithConvertOU2O( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceOU2O( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+OO2O変換) ▼▼▼
function user_qStrStrCopyWithConvertOO2O( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceOO2O( strInput ) );
}
//▼▼▼ 文字列データInput を 文字列データOutput にコピー(+O2OH変換) ▼▼▼
function user_qStrStrCopyWithConvertO2OH( qStrFieldInput, qStrFieldOutput ){
  let strInput =  qbpms.form.get( qStrFieldInput );
  if( strInput === null ){ return; } // ReadOnly source
  if( strInput === ""   ){ return; } // ReadWrite source
  qbpms.form.set( qStrFieldOutput, user_strReplaceO2OH( strInput ) );
}
//▼▼▼ メールアドレス化 ▼▼▼
function user_qStrStrCopyAsEmail( qStrFieldMailboxname, strDomain, qStrFieldEmail ){
  let strMailboxname =  qbpms.form.get( qStrFieldMailboxname );
  if( strMailboxname === null ){ return; } // ReadOnly source
  if( strMailboxname === ""   ){ return; } // ReadWrite source
  let strEmail = strMailboxname.toLowerCase()
                               .replace(/\s+/g, '-') +
                 "@" + strDomain.toLowerCase();
  qbpms.form.set( qStrFieldEmail, strEmail );
}

//▼ Replace to UpperCase / 大文字へ置換 ▼
// ※大文字小文字が存在しない文字(漢字やカタカナなど)は置換されません
function user_strReplaceUpperCase( str ){
  return str.toUpperCase();
}
//▼ Replace to LowerCase / 小文字へ置換 ▼
function user_strReplaceLowerCase( str ){
  return str.toLowerCase();
}
//▼ Capitalize All Words / 全ての単語を大文字始まりへ置換 ▼
function user_strCapitalizeAll( str ){
  return str.replace( /\b[a-z]/g, function(s){ return s.toUpperCase(); });
}
//▼ Replace UU to U / UUをUに置換 ▼
function user_strReplaceUU2U( str ){
  return str.replace( /UU/gi, function(s){ return s.slice( 0, 1 ); });
}
//▼ Replace OU to O / OUをOに置換 ▼
function user_strReplaceOU2O( str ){
  return str.replace( /OU/gi, function(s){ return s.slice( 0, 1 ); });
}
//▼ Replace OO to O / OOをOに置換 ▼
function user_strReplaceOO2O( str ){
  return str.replace( /OO/gi, function(s){ return s.slice( 0, 1 ); });
}
//▼ Replace O to OH / OをOHに置換 ▼
function user_strReplaceO2OH( str ){
  return str.replace( /O/gi, function(s){ return (s + "h"); });
}
//▼ ローマ字変換(カタカナ から Romaji への変換) ▼
function user_strReplaceKatakana2Romaji( str ){
  // <外務省ヘボン式(パスポート式)>
  // ▽https://www.mofa.go.jp/mofaj/toko/passport/download/faq.html#6-Q1
  // ◇氏名のヘボン式ローマ字をどのように書いたらよいか
  // └ ヘボン式ローマ字綴方表 
  //  └ https://www.ezairyu.mofa.go.jp/passport/hebon.html

  const objKatakana2Romaji = {
  // ※長音のOとUについて判定しない(変換時に削除しない)(末尾OUのUは削除)
  // ば行ま行ぱ行に続く撥音はNではなくM
    "ンバ":"mba", "ンビ":"mbi", "ンブ":"mbu", "ンベ":"mbe", "ンボ":"mbo",
    "ンパ":"mpa", "ンピ":"mpi", "ンプ":"mpu", "ンペ":"mpe", "ンポ":"mpo",
    "ンマ":"mma", "ンミ":"mma", "ンム":"mma", "ンメ":"mma", "ンモ":"mma",
  // チチャチュチョに続く促音はTで表記(c)
    "ッチャ":"tcha", "ッチュ":"tchu", "ッチョ":"tcho", "ッチ":"tch",
  // 一般的な促音は子音を重ねて表記(k、t、p、s)
    "ッカ":"kka", "ッキ":"kki", "ック":"kku", "ッケ":"kke", "ッコ":"kko",
    "ッタ":"tta",               "ッツ":"ttsu","ッテ":"tte", "ット":"tto",
    "ッパ":"ppa", "ッピ":"ppi", "ップ":"ppu", "ッペ":"ppe", "ッポ":"ppo",
    "ッサ":"ssa", "ッシ":"sshi","ッス":"ssu", "ッセ":"sse", "ッソ":"sso",
  // 特殊な拗音(ァ、ィ、ゥ、ェ、ォ)
    "シェ":"shie","ジェ":"jie", "チェ":"chie",
    "ティ":"tei", "ディ":"dei",
    "デュ":"deyu",
                  "ニィ":"nii",               "ニェ":"nie",
    "ファ":"fua", "フィ":"fui",               "フェ":"fue", "フォ":"fuo",
                  "ウィ":"ui",                "ウェ":"ue",  "ウォ":"uo",
    "ヴァ":"ba",  "ヴィ":"bi",                "ヴェ":"be",  "ヴォ":"bo",
    //"ヴァ":"bua", "ヴィ":"bui", "ヴェ":"bue", "ヴォ":"buo",
  // 一般的な拗音(ヮは想定しない)
    "キャ":"kya", "キュ":"kyu", "キョ":"kyo",
    "シャ":"sha", "シュ":"shu", "ショ":"sho",
    "チャ":"cha", "チュ":"chu", "チョ":"cho",
    "ニャ":"nya", "ニュ":"nyu", "ニョ":"nyo",
    "ヒャ":"hya", "ヒュ":"hyu", "ヒョ":"hyo",
    "ミャ":"mya", "ミュ":"myu", "ミョ":"myo",
    "リャ":"rya", "リュ":"ryu", "リョ":"ryo",
    "ギャ":"gya", "ギュ":"gyu", "ギョ":"gyo",
    "ジャ":"ja",  "ジュ":"ju",  "ジョ":"jo",
    "ビャ":"bya", "ビュ":"byu", "ビョ":"byo",
    "ピャ":"pya", "ピュ":"pyu", "ピョ":"pyo",
  // 一般的な50音
    "ア":"a",  "イ":"i",  "ウ":"u",  "エ":"e",  "オ":"o",
    "カ":"ka", "キ":"ki", "ク":"ku", "ケ":"ke", "コ":"ko",
    "サ":"sa", "シ":"shi","ス":"su", "セ":"se", "ソ":"so",
    "タ":"ta", "チ":"chi","ツ":"tsu","テ":"te", "ト":"to",
    "ナ":"na", "ニ":"ni", "ヌ":"nu", "ネ":"ne", "ノ":"no",
    "ハ":"ha", "ヒ":"hi", "フ":"fu", "ヘ":"he", "ホ":"ho",
    "マ":"ma", "ミ":"mi", "ム":"mu", "メ":"me", "モ":"mo",
    "ヤ":"ya",            "ユ":"yu",            "ヨ":"yo",
    "ラ":"ra", "リ":"ri", "ル":"ru", "レ":"re", "ロ":"ro",
    "ワ":"wa", "ヰ":"i",             "ヱ":"e",  "ヲ":"o",  "ン":"n",
                          "ヴ":"bu",
    "ガ":"ga", "ギ":"gi", "グ":"gu", "ゲ":"ge", "ゴ":"go",
    "ザ":"za", "ジ":"ji", "ズ":"zu", "ゼ":"ze", "ゾ":"zo",
    "ダ":"da", "ヂ":"ji", "ヅ":"zu", "デ":"de", "ド":"do",
    "バ":"ba", "ビ":"bi", "ブ":"bu", "ベ":"be", "ボ":"bo",
    "パ":"pa", "ピ":"pi", "プ":"pu", "ペ":"pe", "ポ":"po",
  // 各種記号を削除
    "。": "", "、": "", "ー": "", "「": "", "」": "", "・": ""
  };
  const regKatakana = new RegExp( '(' + Object.keys( objKatakana2Romaji ).join('|') + ')', 'g' );
  return str.replace( regKatakana, function(s){ return objKatakana2Romaji[s]; })
            .replace( /ou\b/g, function(s){ return s.slice( 0, -1 ); }); // 末尾OUのUは削除
}
</script>

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_GivennameKatakana
  • → STRING (STRING_TEXTFIELD): q_GivennameRomanized
    • ← STRING (STRING_TEXTFIELD): q_GivennameRomanized
HTML/JavaScript (click to open)
<button onclick='user_qStrStrCopyWithConvertKataRoma(  "q_GivennameKatakana", "q_GivennameRomanized" );'
 type='button'>カナ⇒romaji</button>
<button onclick='user_qStrStrCopyWithCapitalizeAll   ( "q_GivennameRomanized", "q_GivennameRomanized" );'
 type='button'>romaji⇒Romaji</button>
<br>
※長音の削除:
<button onclick='user_qStrStrCopyWithConvertUU2U( "q_GivennameRomanized", "q_GivennameRomanized" );'
 type='button'>"uu"⇒"u"</button>
<button onclick='user_qStrStrCopyWithConvertOU2O( "q_GivennameRomanized", "q_GivennameRomanized" );'
 type='button'>"ou"⇒"o"</button>
<button onclick='user_qStrStrCopyWithConvertOO2O( "q_GivennameRomanized", "q_GivennameRomanized" );'
 type='button'>"oo"⇒"o"</button>
<button onclick='user_qStrStrCopyWithConvertO2OH( "q_GivennameRomanized", "q_GivennameRomanized" );'
 type='button'>("o"⇒"oh")</button>
<br>
※長音UU/OU/OOの判定ルール: <a href="https://www.mofa.go.jp/mofaj/toko/passport/download/faq.html#6-Q1" target="_Blank">外務省ヘボン式〔パスポート式〕</a>

Enter Number using NumPad Buttons

Enter Number using NumPad Buttons

Input / Output

  • ⇆ NUMERIC (DECIMAL): q_Cost (update)
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>

Set Geolocation Data

Obtains the user's current location. ユーザの現在位置を取得します。 (W3C Geolocation API)

Online DEMO (public form)

Input / Output

  • → STRING (STRING_TEXTFIELD) q_LatitudeLongitude (ReadWrite)
  • span#user_GeolocationAPI_Status
HTML/JavaScript (click to open)
Set Geolocation Data
<script>
/*
Decoration Name: Set Geolocation Data
License: MIT
Description: To obtain the user's current location. ユーザの現在位置を取得します。 (W3C Geolocation API)
*/

function user_setLatitudeLongitude( qStrField ){
// https://developer.mozilla.org/docs/Web/API/Geolocation_API/Using_the_Geolocation_API#examples

  let elApiStatus = document.querySelector( "#user_GeolocationAPI_Status" );

  if( !navigator.geolocation ) {
    elApiStatus.textContent = 'Geolocation is not supported by your browser';
  } else {
    elApiStatus.textContent = 'Locating…';
    navigator.geolocation.getCurrentPosition(success, error);
  }
  function success( position ) {
    qbpms.form.set( qStrField, position.coords.latitude + "," + position.coords.longitude );
    elApiStatus.textContent = "";
  }
  function error() {
    elApiStatus.textContent = 'Unable to retrieve your location';
  }

  navigator.geolocation.getCurrentPosition( success, error );
}
</script>

<button type="button"
 onclick="user_setLatitudeLongitude(
               'q_LatitudeLongitude'
          )"><span class="material-icons">my_location</span></button>

<button type="button"
 onclick="qbpms.form.set( 'q_LatitudeLongitude', null )"
><span class="material-icons">backspace</span></button>

<span id="user_GeolocationAPI_Status"></span>

Check Geolocation Data

Google Map buttons such as route and street view will appear. You can check another tab just by clicking. 
 経路やストリートビューといった Google Map ボタンが表示されます。クリックすると別タブで確認できます。

Online DEMO (public form)

Input / Output

  • ← STRING (STRING_TEXTFIELD) q_LatitudeLongitude (ReadOnly)
  • → browsing context (new tab)
HTML/JavaScript (click to open)
Check Geolocation Data (Guide Panel) 
<script>
/*
Decoration Name: Check Geolocation Data
License: MIT
Description:
 Google Map buttons such as route and street view will appear. You can check another tab just by clicking. 
 経路やストリートビューといった Google Map ボタンが表示されます。クリックすると別タブで確認できます。
▼https://developers.google.com/maps/documentation/urls/get-started
◆Google Maps URLs -- Universal cross-platform syntax (No API key required)
- Search:     e.g., https://www.google.com/maps/search/?api=1&query=35.0110379%2C135.7617619
- Satellite:  e.g., https://www.google.com/maps/@?api=1&map_action=map&basemap=satellite&zoom=18¢er=35.026%2C135.779
- Directions: e.g., https://www.google.com/maps/dir/?api=1&destination=35.0115636%2C135.7516171
- StreetView: e.g., https://www.google.com/maps/@?api=1&map_action=pano&pitch=25&viewpoint=34.9865559%2C135.7596661
*/

function user_encodeLatitudeLongitude( strLatitudeLongitude ){
  if( typeof( strLatitudeLongitude ) !== "string" ){
    console.error( "\n DecorationError:" +
                   " 'strLatitudeLongitude' must be string \n" );
    return null;
  }
  let strTmp = strLatitudeLongitude.replace( /\s/g, "" );
  // Remove all single white space character, including space, tab, feed, and other Unicode spaces.
  const regLatLng = /^((\-?|\+?)?\d+(\.\d+)?),((\-?|\+?)?\d+(\.\d+)?)$/; // RegExp
  if( regLatLng.test( strTmp ) === false ){
    console.error( "\n DecorationError:" +
                   " 'strLatitudeLongitude' must be comma-separated lat/lng coordinates. \n" );
    return null;
  }
  return encodeURIComponent( strTmp );
}

function user_openPinnedMap( strLatitudeLongitude ){
  let strTmp = user_encodeLatitudeLongitude( strLatitudeLongitude );
  if( strTmp === null ){ return; }
  console.log( 'https://www.google.com/maps/search/?api=1&query=' + strTmp );
  window.open( 'https://www.google.com/maps/search/?api=1&query=' + strTmp );
}
function user_openSatelliteMap( strLatitudeLongitude ){
  let strTmp = user_encodeLatitudeLongitude( strLatitudeLongitude );
  if( strTmp === null ){ return; }
  console.log( 'https://www.google.com/maps/@?api=1&map_action=map&basemap=satellite&zoom=18¢er=' + strTmp );
  window.open( 'https://www.google.com/maps/@?api=1&map_action=map&basemap=satellite&zoom=18¢er=' + strTmp );
}
function user_openDirectionsMap( strLatitudeLongitude ){
  let strTmp = user_encodeLatitudeLongitude( strLatitudeLongitude );
  if( strTmp === null ){ return; }
  console.log( 'https://www.google.com/maps/dir/?api=1&destination=' + strTmp );
  window.open( 'https://www.google.com/maps/dir/?api=1&destination=' + strTmp );
}
function user_openStreetViewMap( strLatitudeLongitude ){
  let strTmp = user_encodeLatitudeLongitude( strLatitudeLongitude );
  if( strTmp === null ){ return; }
  console.log( 'https://www.google.com/maps/@?api=1&map_action=pano&pitch=25&viewpoint=' + strTmp );
  window.open( 'https://www.google.com/maps/@?api=1&map_action=pano&pitch=25&viewpoint=' + strTmp );
}
</script>

<button type="button"
 onclick="user_openPinnedMap(
            qbpms.form.get( 'q_LatitudeLongitude' )
          )"><span class="material-icons">place</span></button>
<button type="button"
 onclick="user_openDirectionsMap(
            qbpms.form.get( 'q_LatitudeLongitude' )
          )"><span class="material-icons">directions</span></button>
<button type="button"
 onclick="user_openSatelliteMap(
            qbpms.form.get( 'q_LatitudeLongitude' )
          )"><span class="material-icons">satellite</span></button>
<button type="button"
 onclick="user_openStreetViewMap(
            qbpms.form.get( 'q_LatitudeLongitude' )
          )"><span class="material-icons">panorama</span></button>

Confirm Selected User

Confirm Selected User

Input / Output

  • ← QUSER q_Reviewer
  • span#user_footnote_Reviewer
HTML/JavaScript (click to open)
<span id="user_footnote_Reviewer"></span><br>

<script>
qbpms.form.on( 'ready',                user_setFootnote_Reviewer ); // for Initial Value
qbpms.form.on( 'change', 'q_Reviewer', user_setFootnote_Reviewer );
function user_setFootnote_Reviewer(){
  let quserTarget = qbpms.form.get( "q_Reviewer" );
  if( quserTarget === null ){ return; }
  let strInnerHtml =  '';
      strInnerHtml += 'uid: <span style="font-style:italic; color:#999999">';
      strInnerHtml += quserTarget.id;
      strInnerHtml += '</span>, email: <span style="font-weight:bold; color:#009900">';
      strInnerHtml += quserTarget.email;
      strInnerHtml += '</span>';
  document.querySelector( '#user_footnote_Reviewer' ).innerHTML = strInnerHtml;
}
</script>

Confirm Selected Value

Confirm Selected Value

Input / Output

  • ← SELECT (SELECT_SINGLE): q_Currency
  • span#user_notice01
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
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
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
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>

Calculate Working Hours

Calculate Working Hours (W3C, Web Animations API)
Calculates the difference (elapsed time) between Start time data and End time data. The values exceeding 8 hours are displayed in red. The values exceeding 12 hours or negative values are displayed in purple. (Bar Chart)
Start時刻データとEnd時刻データの差分(経過時間)を計算します。「8時間を超える値」となった場合、赤色の字で表示されます。「12時間を超える値」や「マイナスの値」となった場合には、紫色の字で表示されます。(棒グラフ表示付き)

Online DEMO (public form)

Input / Output

  • ← DATETIME: q_StartTime
  • ← DATETIME: q_EndTime
  • ← NUMERIC (2 decimal places): q_BreakTimes
    • span#user_WorkingHours (update)
HTML/JavaScript (q_StartTime)
<script>
function user_setQDatetime( qfieldName, numHour = 0, numMin = 0 ){
  let dateTmp      = new Date();
  let dateSource   = qbpms.form.get( qfieldName );
  if( dateSource !== null ){
    dateTmp.setFullYear( dateSource.getFullYear() );
    dateTmp.setDate    ( dateSource.getDate()     );
  }
  dateTmp.setHours   ( numHour );
  dateTmp.setMinutes ( numMin );
  qbpms.form.set( qfieldName, dateTmp );
}
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>

<button type='button'
 onclick='user_setQDatetime( "q_StartTime", 9, 0 );
'>09:00</button>
<button type='button'
 onclick='user_setQDatetime( "q_StartTime", 9, 30 );
'>09:30</button>
<button type='button'
 onclick='user_setQDatetime( "q_StartTime", 10, 0 );
'>10:00</button>
<button type='button'
 onclick='user_setQDatetime( "q_StartTime", 12, 0 );
'>12:00</button>
<br />
<button type='button'
 onclick='user_addYmdQDate( "q_StartTime", 0, 0, 0, -1 )'>-1:00</button>
<button type='button'
 onclick='user_addYmdQDate( "q_StartTime", 0, 0, 0, 0, -10 )'>-0:10</button>
<button type='button'
 onclick='user_addYmdQDate( "q_StartTime", 0, 0, 0, 0, -1 )'>-0:01</button>
<button type='button'
 onclick='user_addYmdQDate( "q_StartTime", 0, 0, 0, 0, 1 )'>+0:01</button>
<button type='button'
 onclick='user_addYmdQDate( "q_StartTime", 0, 0, 0, 0, 10 )'>+0:10</button>
<button type='button'
 onclick='user_addYmdQDate( "q_StartTime", 0, 0, 0, 1 )'>+1:00</button>
HTML/JavaScript (q_EndTime)
<button type='button'
 onclick='user_setQDatetime( "q_EndTime", 14, 30 );
'>14:30</button>
<button type='button'
 onclick='user_setQDatetime( "q_EndTime", 15, 0 );
'>15:00</button>
<button type='button'
 onclick='user_setQDatetime( "q_EndTime", 17, 0 );
'>17:00</button>
<button type='button'
 onclick='user_setQDatetime( "q_EndTime", 18, 0 );
'>18:00</button>
<br />
<button type='button'
 onclick='user_addYmdQDate( "q_EndTime", 0, 0, 0, -1 )'>-1:00</button>
<button type='button'
 onclick='user_addYmdQDate( "q_EndTime", 0, 0, 0, 0, -10 )'>-0:10</button>
<button type='button'
 onclick='user_addYmdQDate( "q_EndTime", 0, 0, 0, 0, -1 )'>-0:01</button>
<button type='button'
 onclick='user_addYmdQDate( "q_EndTime", 0, 0, 0, 0, 1 )'>+0:01</button>
<button type='button'
 onclick='user_addYmdQDate( "q_EndTime", 0, 0, 0, 0, 10 )'>+0:10</button>
<button type='button'
 onclick='user_addYmdQDate( "q_EndTime", 0, 0, 0, 1 )'>+1:00</button>
HTML/JavaScript (q_BreakTimes)
<button type='button'
 onclick='qbpms.form.set( "q_BreakTimes", 0 )'>0.0 h</button>
<button type='button'
 onclick='qbpms.form.set( "q_BreakTimes", 0.5 )'>0.5 h</button>
<button type='button'
 onclick='qbpms.form.set( "q_BreakTimes", 1 )'>1.0 h</button>
HTML/JavaScript (Guide Panel)
<div id="user_WorkingHours"></div>
<script>
/* Calculate Working Hours
Calculates the difference (elapsed time) between Start time data and End time data. The values exceeding 8 hours are displayed in red. The values exceeding 12 hours or negative values are displayed in purple. (Bar Chart)
Start時刻データとEnd時刻データの差分(経過時間)を計算します。「8時間を超える値」となった場合、赤色の字で表示されます。「12時間を超える値」や「マイナスの値」となった場合には、紫色の字で表示されます。(棒グラフ表示付き)
*/

qbpms.form.on ( 'ready',                  user_calcQdatetimeDiff );
qbpms.form.on ( 'change', 'q_StartTime',  user_calcQdatetimeDiff );
qbpms.form.on ( 'change', 'q_EndTime',    user_calcQdatetimeDiff );
qbpms.form.on ( 'change', 'q_BreakTimes', user_calcQdatetimeDiff );

function user_calcQdatetimeDiff () {
  const qDatetimeStart = 'q_StartTime';            ///// ★★★ EDIT HERE ★★★
  const qDatetimeEnd   = 'q_EndTime';              ///// ★★★ EDIT HERE ★★★
  const selectorId     = 'user_WorkingHours';      ///// ★★★ EDIT HERE ★★★
  const qNumericBreak  = 'q_BreakTimes';            ///// ★★★ EDIT HERE ★★★

  const strZoneAcolor  = '#009900'; // green         ///// ★★ to customize ★★
  const numZoneAhour   = 7;         // upto 7h       ///// ★★ to customize ★★
  const strZoneBcolor  = '#87c645'; // light green   ///// ★★ to customize ★★
  const numZoneBhour   = 8;         // upto 8h       ///// ★★ to customize ★★
  const strZoneCcolor  = '#EF9A9A'; // red           ///// ★★ to customize ★★
  const numZoneChour   = 12;        // upto 12h      ///// ★★ to customize ★★
  const strZoneDcolor  = '#990099'; // purple        ///// ★★ to customize ★★
  const numZoneDhour   = 16; // Container width=100% ///// ★★ to customize ★★

  //// Container: remove all children
  let elContainer = document.querySelector( "#" + selectorId );
  elContainer.textContent     = 'Working Hours:';

  //// Container: draw dotted box
  elContainer.style.border    = '2px dotted';
  elContainer.style.width     = '90%';
  elContainer.style.minHeight = '1.8em';
  elContainer.style.margin    = '0.5em 5% 0 0';
  elContainer.style.maxWidth  = '1000px';
  let dateStart = qbpms.form.get ( qDatetimeStart );
  if( dateStart === null ){ console.log( " Start===null" ); return; }
  let dateEnd   = qbpms.form.get ( qDatetimeEnd   );
  if( dateEnd   === null ){ console.log( " End===null" ); return; }

  let bigBreak  = qbpms.form.get ( qNumericBreak );
  console.log( " Break Times: " + bigBreak + " (h)" );
  let numBreak  = bigBreak ? bigBreak : 0;

  //// Calc length of bar chart
  let numMinute = ( dateEnd.getTime() - dateStart.getTime() ) /1000 /60 -
                  ( numBreak *60 );
  let numHour   = Math.ceil ( numMinute *100 /60 ) /100;
  console.log( " Working Hours: " + numHour + " (h)" );

  let strHour   = numHour.toFixed(2) + " h";       // e.g. "7.99 h", "15.99 h", "100.0 h"
  let strHHmm   = ( Math.floor ( numMinute / 60 ) ) +
                  ":" + ( "0" + ( numMinute % 60 ) ).slice(-2); // e.g. "07:59", "15:59", "100:00"
  let numWidthA = 0;
  let numWidthB = 0;
  let numWidthC = 0;
  let numWidthD = 0;
  if( numHour < 0 ){
  }else if( numHour <= numZoneAhour ){
    numWidthA = Math.ceil ( numHour                       / numZoneDhour *100);
  }else if( numHour <= numZoneBhour ){
    numWidthA = Math.ceil ( numZoneAhour                  / numZoneDhour *100 );
    numWidthB = Math.ceil ( (numHour      - numZoneAhour) / numZoneDhour *100 );
  }else if( numHour <= numZoneChour ){
    numWidthA = Math.ceil ( numZoneAhour                  / numZoneDhour *100 );
    numWidthB = Math.ceil ( (numZoneBhour - numZoneAhour) / numZoneDhour *100 );
    numWidthC = Math.ceil ( (numHour      - numZoneBhour) / numZoneDhour *100 );
  }else if( numHour < numZoneDhour ){
    numWidthA = Math.ceil ( numZoneAhour                  / numZoneDhour *100 );
    numWidthB = Math.ceil ( (numZoneBhour - numZoneAhour) / numZoneDhour *100 );
    numWidthC = Math.ceil ( (numZoneChour - numZoneBhour) / numZoneDhour *100 );
    numWidthD = Math.ceil ( (numHour      - numZoneChour) / numZoneDhour *100 );
  }else if( numHour >= numZoneDhour ){
    numWidthD = 100;
  }

  //// Calc offset of Label
  let numLeft  = Math.ceil ( numHour / numZoneDhour *100 );
  if( numLeft < 0 )   { numLeft = 0; }
  if( numLeft >= 100 ){ numLeft = 0; }

  //// Label for Diff Data: 
  let elLabel             = document.createElement( "div" );
  let elHour              = document.createElement( "span" );
  elHour.style.zIndex     = 2;
  elHour.style.position   = 'relative';
  elHour.style.left       = numLeft + "%";
  elLabel.append( elHour );
  elContainer.append( elLabel );

  //// Bar Chart configs for user recognition:
  let elBarBox            = document.createElement( "div" );
  elBarBox.style.width    = '100%';
  elBarBox.style.whiteSpace = "nowrap"; // Prevention of line feed code insertion
  let elBarA              = document.createElement( "span" );
  elBarA.style.display    = 'inline-block';
  elBarA.style.background = strZoneAcolor;
  elBarA.style.height     = '0.8em';
  elBarBox.append( elBarA );
  let elBarB              = document.createElement( "span" );
  elBarB.style.display    = 'inline-block';
  elBarB.style.background = strZoneBcolor;
  elBarB.style.height     = '0.8em';
  elBarBox.append( elBarB );
  let elBarC              = document.createElement( "span" );
  elBarC.style.display    = 'inline-block';
  elBarC.style.background = strZoneCcolor;
  elBarC.style.height     = '0.8em';
  elBarBox.append( elBarC );
  let elBarD              = document.createElement( "span" );
  elBarD.style.display    = 'inline-block';
  elBarD.style.background = strZoneDcolor;
  elBarD.style.height     = '0.8em';
  elBarBox.append( elBarD );
  elContainer.append( elBarBox );

  //// Bar Animation:
  if( numHour < 0 ){                  ////// Less than 0 h //////
    elHour.innerHTML        = '<span class="material-icons">warning</span>' +
                              ' <strong>Negative Value / マイナス値です</strong>';
    elHour.style.color      = strZoneDcolor;

  }else if( numHour <= numZoneAhour ){ ////// 7h or less //////
    elHour.innerHTML        = '<span class="material-icons">sentiment_satisfied_alt</span> ' +
                              strHour + ' (' + strHHmm + ')';
    elHour.style.color      = strZoneAcolor;
    elBarA.animate([ /// W3C, Web Animations API, Element.animate()
      { width: '0%' },
      { width: numWidthA + "%" }
    ],{duration:700,fill:'forwards',easing:'linear'}); /// KeyframeEffect
    // CAUTION: W3C, Web Animations: Working Draft (asof 20220929)

  }else if( numHour <= numZoneBhour ){ ////// Over 7h //////
    elHour.innerHTML        = '<span class="material-icons">sentiment_neutral</span> ' +
                              strHour + ' (' + strHHmm + ')';
    elHour.style.color      = strZoneAcolor; // or strZoneBcolor;
    elBarA.animate([
      { width: '0%' },
      { width: numWidthA + "%" }
    ],{duration:700,fill:'forwards',easing:'linear'});
    elBarB.animate([
      { width: '0%' },
      { width: numWidthB + "%" }
    ],{delay:700,duration:400,fill:'forwards',easing:'ease-out'});

  }else if( numHour <= numZoneChour ){ ////// Over 8h //////
    elHour.innerHTML        = '<span class="material-icons">sentiment_dissatisfied</span> ' +
                              strHour + ' (' + strHHmm + ')';
    elHour.style.color      = "#D50000"; // or strZoneCcolor;
    elBarA.animate([
      { width: '0%' },
      { width: numWidthA + "%" }
    ],{duration:700,fill:'forwards',easing:'linear'});
    elBarB.animate([
      { width: '0%' },
      { width: numWidthB + "%" }
    ],{delay:700,duration:100,fill:'forwards',easing:'linear'});
    elBarC.animate([
      { width: '0%' },
      { width: numWidthC + "%" }
    ],{delay:800,duration:1200,fill:'forwards',easing:'ease-out'});

  }else if( numHour < numZoneDhour ){ ////// Over 12h //////
    elHour.innerHTML        = '<span class="material-icons">sentiment_very_dissatisfied</span> ' +
                              '<strong>' + strHour + ' (' + strHHmm + ')</strong>';
    elHour.style.color      = strZoneDcolor;
    elBarA.animate([
      { width: '0%' },
      { width: numWidthA + "%" }
    ],{duration:700,fill:'forwards',easing:'linear'});
    elBarB.animate([
      { width: '0%' },
      { width: numWidthB + "%" }
    ],{delay:700,duration:100,fill:'forwards',easing:'linear'});
    elBarC.animate([
      { width: '0%' },
      { width: numWidthC + "%" }
    ],{delay:800,duration:800,fill:'forwards',easing:'linear'});
    elBarD.animate([
      { width: '0%' },
      { width: numWidthD + "%" }
    ],{delay:1600,duration:1600,fill:'forwards',easing:'ease-out'});

  }else if( numHour >= numZoneDhour ){  ////// Over 12h //////
    elHour.innerHTML        = '<span class="material-icons">warning</span>' +
                              '<strong>' + strHour + ' (' + strHHmm + ')</strong>' +
                              ' ← Are you sure? / 正しい値ですか?';
    elHour.style.color      = strZoneDcolor;
    elBarD.animate([
      { width: '0%' },
      { width: numWidthD + "%" }
    ],{duration:700,fill:'forwards',easing:'ease-in'});
  }
}
</script>

Copy to Clipboard

Copy to Clipboard

Input / Output

  • ← STRING (STRING_TEXTAREA): q_SalesTSV
  • → clipboard
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>

The Tab codes "\t" stored in Read-only qString will NOT be copied.


Validate with Regular Expressions

Validate with Regular Expressions

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_Email
  • span#user_footnote_Email
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>

Show Process Detail

Input / Output

  • ← STRING: q_Process_ID
  • → (separated window or tab)
HTML/JavaScript (click to open)
Show Process Detail: <button type="button" onclick="user_showProcessDetail('q_Process_ID','#user_msg')">Desktop View</button>
<button type="button" onclick="user_showProcessDetail('q_Process_ID','#user_msg', 'mobile')">Mobile View</button><br>
<span id="user_msg"></span>

<script>

//対象の文字型データ項目の値がプロセスIDの場合、該当するプロセス詳細画面が別ウィンドウ(タブ)で表示されます。
//プロセスIDと判断される文字のフォーマットは、 user_parsePRocessId 関数の説明をご覧ください。
//If the value of the target String type data item is a process ID, the corresponding process detail is displayed in a separate window (or tab).
//See the description of the user_parseProcessId function for the format of characters that are determined to be process IDs.

function user_showProcessDetail(qfieldName, spanId, type){
    const qfieldValue = qbpms.form.get(qfieldName);
    if(qfieldValue !== null){
        const processId = user_parseProcessId(qfieldValue.trim());
        let usermsg = '';
        if(processId !== '-1'){
            usermsg += new Date().toLocaleTimeString() + ' Process Detail(p' + processId +') is displayed in a separate window(or tab).';
            window.open(user_createProcessDetailUrl(processId, type), '_blank');
        }else{
            usermsg += '<span style="color:#ff0000">\'' + qfieldValue.trim() +'\' is not process ID!</span>';
        }
        document.querySelector(spanId).innerHTML = usermsg;
    }
}

/*
文字列がプロセスIDかどうかを解析する。
次のフォーマットの文字列をプロセスIDと判断。
- 数字だけで構成される。ただし1文字は0ではない。
- 'p' で始まり、続く文字は全て数字である。'p' の後ろの数字は0ではない。
@param {string} text 対象の文字列
@return {string} プロセスID(数字のみで構成)。プロセスIDではない場合、'-1'。
*/
function user_parseProcessId(text){
    const regex = /^p?[1-9]{1}\d*$/g;
    const match = text.match(regex);
    if(match !== null){
        if(match[0].startsWith('p')){
            return match[0].slice(1,match[0].length);
        }else{
            return match[0];
        }
    }else{
        return '-1';
    }
}

/*
プロセスIDからプロセス詳細画面URLが生成されます。
ワークフロー基盤のアクセスURLは、https://s.questetra.net/xxxxxxxx, https://xxxxxxxx.questetra.net/ の両方に対応しています。
@param {string} processId
@param {string} type スマートフォンの画面URLを取得する場合、'mobile'を指定。
@return {string} プロセス詳細画面のURL。
*/
function user_createProcessDetailUrl(processId, type){

    let processDetailPath = '/OR/ProcessInstance/view?processInstanceId=' + processId;
    if(type !== null && type === 'mobile'){
        processDetailPath = '/SP' + processDetailPath;
    }

    const hostname = location.hostname;

    if(hostname.startsWith('s.')){
    //in case of Access URL -> https://s.qustetra.net/xxxxxxxx/
        return 'https://' + hostname + '/' + location.pathname.split('/')[1] + processDetailPath;
    }else{
        return 'https://' + hostname + processDetailPath;
    }

}
</script>

Extract Process IDs

Input / Output

  • ← STRING: q_Related_Process_IDs
  • → span#user_msg
HTML/JavaScript (click to open)
Extract Process IDs / プロセスID 抽出: <button type="button" onclick="user_createProcessDetailButton('q_Related_Process_IDs','#user_msg')">Desktop View</button>
<button type="button" onclick="user_createProcessDetailButton('q_Related_Process_IDs','#user_msg', 'mobile')">Mobile View</button>
<div><span id="user_msg"></span></div>

<script>
function user_createProcessDetailButton(qfieldName, spanId, type){
    const qfieldValue = qbpms.form.get(qfieldName);
    if(qfieldValue === null){
        return;
    }
    const pidArray = user_extractProcessIds(qfieldValue);

    let usermsg = '';
    if(pidArray === null){
        usermsg += 'There are no Process IDs.';
    }else{
        pidArray.forEach((element)=>{
            let processId = element;
            usermsg += '<a class="processDetailBtn" href="' + user_createProcessDetailUrl(processId, type) + '" target="_blank">p' + processId + '<span class="material-icons">open_in_new</span></a> ';
        });
    }

    document.querySelector(spanId).innerHTML = usermsg;

}

/*
文字列の中からプロセスID(複数)を抽出。
次のフォーマットの文字列をプロセスIDと判断。
- 数字だけで構成される。ただし1文字は0ではない。
- 'p' で始まり、続く文字は全て数字である。'p' の後ろの数字は0ではない。
@param {string} text 対象の文字列
@return {array} プロセスIDを要素とする Array オブジェクト。プロセスIDがない場合は null。
*/
function user_extractProcessIds(text){
    const regex = /((^p[1-9])|(\s+p[1-9])|(^[1-9])|(\s+[1-9]))\d*/g;
    const match = text.match(regex);

    if(match === null){
        return null;
    }

    const array = new Array();
    match.forEach((element) => {
        let incompleteProcessId = element.trim();
        if(incompleteProcessId.startsWith('p')){
            array.push(incompleteProcessId.slice(1, incompleteProcessId.length));
        }else{
            array.push(incompleteProcessId);
        }
    });
    return array;

}

/*
プロセスIDからプロセス詳細画面URLが生成されます。
ワークフロー基盤のアクセスURLは、https://s.questetra.net/xxxxxxxx, https://xxxxxxxx.questetra.net/ の両方に対応しています。
@param {string} processId
@param {string} type スマートフォンの画面URLを取得する場合、'mobile'を指定。
@return {string} プロセス詳細画面のURL。
*/
function user_createProcessDetailUrl(processId, type){
    
    let processDetailPath = '/OR/ProcessInstance/view?processInstanceId=' + processId;
    if(type !== null && type === 'mobile'){
        processDetailPath = '/SP' + processDetailPath;
    }

    const hostname = location.hostname;

    if(hostname.startsWith('s.')){
    //in case of Access URL -> https://s.qustetra.net/xxxxxxxx/
        return 'https://' + hostname + '/' + location.pathname.split('/')[1] + processDetailPath;
    }else{
        return 'https://' + hostname + processDetailPath;
    }

}

</script>
<style>
a.processDetailBtn{
    line-height: 2.2;
    border-radius: 0.5rem;
    border-style: solid;
    border-width: 0.5px;
    border-color: #009FE8;
    padding-top:0.2rem;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
    color:#000;
    background-color: #D1E1E8;
}
a.processDetailBtn:hover{
    color: #fff;
    background-color: #009FE8;
}
</style>

Validate using Check-Digit

Validate using Check-Digit 
Japan's Corporate Number / 法人番号 q_CorporateNumber
Japan's Individual Number / 個人番号(マイナンバー) q_IndividualNumber

Online DEMO (public form)

Input / Output

  • ⇆ STRING (STRING_TEXTFIELD): q_CorporateNumber
HTML/JavaScript (click to open)
<span id="user_footnote_CorporateNumber"></span><br>

<script>
qbpms.form.on( 'ready',                       user_setFootnote_CorporateNumber );
qbpms.form.on( 'change', 'q_CorporateNumber', user_setFootnote_CorporateNumber );

function user_setFootnote_CorporateNumber(){
  const qfieldName     = "q_CorporateNumber";               //// EDIT ////
  const strSelector    = "#user_footnote_CorporateNumber";  //// EDIT ////

  const regDigit13     = /^\d{13}$/;
  const strTarget      = qbpms.form.get( qfieldName );
  console.log( " strTarget: " + strTarget );
  let   strInnerHtml   = "";
  if( regDigit13.test( strTarget ) === false ){
    strInnerHtml  = '';
    strInnerHtml += '<span style="color:#ffa500">';
    strInnerHtml += 'NOT 13 digit / 13桁ではありません';
    strInnerHtml += '</span>';
  }else{
    if( isCorporateNumber( strTarget ) ){
      strInnerHtml  = '';
      strInnerHtml += '<span style="color:#0000ff">';
      strInnerHtml += '✓ valid / 有効です'; // &#10003;
      strInnerHtml += '</span>';
    }else{
      strInnerHtml  = '';
      strInnerHtml += '<span style="color:#ff0000">';
      strInnerHtml += '✗ invalid / 問題があります'; // &cross = &#10007;
      strInnerHtml += '</span>';
    }
  }
  document.querySelector( strSelector ).innerHTML = strInnerHtml;
}

function isCorporateNumber( str ){
  const regDigit13    = /^\d{13}$/;
  if( regDigit13.test( str ) === false ){ return false; }
  const numCheckDigit = parseInt( str.charAt(0) );
  let numCheckSum = 0;
      numCheckSum += parseInt( str.charAt(1) ) * 2;
      numCheckSum += parseInt( str.charAt(2) );
      numCheckSum += parseInt( str.charAt(3) ) * 2;
      numCheckSum += parseInt( str.charAt(4) );
      numCheckSum += parseInt( str.charAt(5) ) * 2;
      numCheckSum += parseInt( str.charAt(6) );
      numCheckSum += parseInt( str.charAt(7) ) * 2;
      numCheckSum += parseInt( str.charAt(8) );
      numCheckSum += parseInt( str.charAt(9) ) * 2;
      numCheckSum += parseInt( str.charAt(10));
      numCheckSum += parseInt( str.charAt(11)) * 2;
      numCheckSum += parseInt( str.charAt(12));
  if( numCheckDigit === (9 - numCheckSum % 9) ){
    return true;
  }else{
    return false;
  }
}
</script>

Input / Output

  • ⇆ STRING (STRING_TEXTFIELD): q_IndividualNumber
HTML/JavaScript (click to open)
<span id="user_footnote_IndividualNumber"></span><br>

<script>
qbpms.form.on( 'ready',                        user_setFootnote_IndividualNumber );
qbpms.form.on( 'change', 'q_IndividualNumber', user_setFootnote_IndividualNumber );

function user_setFootnote_IndividualNumber(){
  const qfieldName     = "q_IndividualNumber";               //// EDIT ////
  const strSelector    = "#user_footnote_IndividualNumber";  //// EDIT ////

  const regDigit12     = /^\d{12}$/;
  const strTarget      = qbpms.form.get( qfieldName );
  console.log( " strTarget: " + strTarget );
  let   strInnerHtml   = "";
  if( regDigit12.test( strTarget ) === false ){
    strInnerHtml  = '';
    strInnerHtml += '<span style="color:#ffa500">';
    strInnerHtml += 'NOT 12 digit / 12桁ではありません';
    strInnerHtml += '</span>';
  }else{
    if( isIndividualNumber( strTarget ) ){
      strInnerHtml  = '';
      strInnerHtml += '<span style="color:#0000ff">';
      strInnerHtml += '✓ valid / 有効です'; // &check = &#10003;
      strInnerHtml += '</span>';
    }else{
      strInnerHtml  = '';
      strInnerHtml += '<span style="color:#ff0000">';
      strInnerHtml += '✗ invalid / 問題があります'; // &cross = &#10007;
      strInnerHtml += '</span>';
    }
  }
  document.querySelector( strSelector ).innerHTML = strInnerHtml;
}

function isIndividualNumber( str ){
  const regDigit12    = /^\d{12}$/;
  if( regDigit12.test( str ) === false ){ return false; }
  const numCheckDigit = parseInt( str.charAt(11) );
  let numCheckSum = 0;
      numCheckSum += parseInt( str.charAt(0) ) * (11 - 5 );
      numCheckSum += parseInt( str.charAt(1) ) * (10 - 5 );
      numCheckSum += parseInt( str.charAt(2) ) * ( 9 - 5 );
      numCheckSum += parseInt( str.charAt(3) ) * ( 8 - 5 );
      numCheckSum += parseInt( str.charAt(4) ) * ( 7 - 5 );
      numCheckSum += parseInt( str.charAt(5) ) * ( 6 + 1 );
      numCheckSum += parseInt( str.charAt(6) ) * ( 5 + 1 );
      numCheckSum += parseInt( str.charAt(7) ) * ( 4 + 1 );
      numCheckSum += parseInt( str.charAt(8) ) * ( 3 + 1 );
      numCheckSum += parseInt( str.charAt(9) ) * ( 2 + 1 );
      numCheckSum += parseInt( str.charAt(10)) * ( 1 + 1 );
      numCheckSum = 11 - numCheckSum % 11;
  if( numCheckSum % 11 <= 1 ){ numCheckSum = 0; }
  if( numCheckDigit === numCheckSum ){
    return true;
  }else{
    return false;
  }
}
</script>
corporate-number check digit-algorithm
my-number check digit algorithm

Generate QR Code

Generate QR Code image in JavaScript

Input / Output

  • ← STRING (STRING_TEXTAREA) q_Url
  • span#user_output_encode
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
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>

Extract URLs

Input / Output

  • ← STRING: q_text
  • clipboard
HTML/JavaScript (click to open)
<button type="button" onclick="user_getUrlsInText('q_text','#user_msg')">Get URLs</button>
<span id="user_msg"></span>
<script>
// qfieldName で指定されたデータ項目(文字型)の値の中から、URL が抽出されクリップボードにコピーされます。
// /https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-]+/g にマッチする文字列が URL として抽出されます。
// The URL is extracted from the value of the data item (string type) specified by qfieldName and copied to the clipboard.
// /https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-]+/g にマッチする文字列が URL として抽出されます。
// Strings matching /https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-]+/g are extracted as URLs.
function user_getUrlsInText(qfieldName, spanId){
    const text = qbpms.form.get(qfieldName);
    const regex = /https?:\/\/[\w/:%#\$&\?\(\)~\.=\+\-]+/g;
    const matches = text.match(regex);
    let msg = '';
    if(matches != null){
        let urls = '';
        matches.forEach((element) => {
            urls += element + '\n';
        });
        urls = urls.trim();
        navigator.clipboard.writeText(urls);
        msg = new Date().toLocaleTimeString() + ' ' + matches.length + 'URLs copied to the clipboard!';
    }else{
        msg = '<span style="color:#ff0000">No URLs! Clipboard not updated.</span>';
    }
    document.querySelector(spanId).innerHTML = msg;
}
</script>

Export Text as UTF8B-File

Input / Output

  • ← STRING (STRING_TEXTAREA): q_MultilineString
  • download.txt (download)
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 Role Members via REST API

Input / Output

  • button.user_ListQusers
  • span#user_footnote_Translator
    • button.user_SetQuser
    • → QUSER q_Translator
HTML/JavaScript (click to open)
<button type='button' class='user_ListQusers' onclick='
  user_listButtons( "23", "#user_footnote_Translator", "q_Translator" );
'>r81-Native English speaker ↓</button>
<button type='button' class='user_ListQusers' onclick='
  user_listButtons( "24", "#user_footnote_Translator", "q_Translator" );
'>r82-Native Spanish speaker ↓</button>
<br>
<span id="user_footnote_Translator"></span>
<br>

<script>
function user_setByButtonValue_Translator(){  //// EDIT
  let qfieldName          = "q_Translator";   //// EDIT
  let strButtonValue      = this.value; 
  qbpms.form.set( qfieldName, strButtonValue );
}

function user_listButtons( numRoleId, strSpanId, qfieldName ){
  let elSpan = document.querySelector( strSpanId );
  let xhr = new XMLHttpRequest();
  xhr.open( "GET", "/API/User/RoleMembership/listByQrole?id=" + numRoleId );
  // System Settings API - Retrieving all Members of a Role
  // https://online-demo-en.questetra.net/s/swagger/index.html#/RoleMembership/listRoleMembershipsByQrole
  xhr.responseType = 'json';
  xhr.send();

  /* RESPONCE SAMPLE
  {
    roleMemberships: [
      {
        qroleId: 23,
        qroleName: "r81-Native English speaker",
        quserEmail: "SouthPole@questetra.com",
        quserId: 1,
        quserName: "SouthPole"
      },
      { … },
      {
        qroleId: 23,
        qroleName: "r81-Native English speaker",
        quserEmail: "SaintHelena@questetra.com",
        quserId: 10,
        quserName: "SaintHelena"
      }
    ]
  }*/

  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 quser " + objResponse.roleMemberships.length );

      elSpan.innerHTML = ""; // clear
      for (let i = 0; i < objResponse.roleMemberships.length; i++ ) {
        console.log( " quser " + i + ": " + objResponse.roleMemberships[i].quserEmail );
        let elNewButton = document.createElement("button");
        elNewButton.type      = "button";
        elNewButton.title     = // as tooltip
          objResponse.roleMemberships[i].quserId +
          ": " +
          objResponse.roleMemberships[i].quserEmail +
          "";
        elNewButton.value     = objResponse.roleMemberships[i].quserEmail;
        elNewButton.innerHTML = objResponse.roleMemberships[i].quserName;
        elNewButton.classList.add( "user_SetQuser" );
        elNewButton.onclick   = user_setByButtonValue_Translator;  //// EDIT
        elSpan.append( // ...nodesOrDOMStrings
          " ",
          elNewButton
        );
      }
    }
    console.log( " Decoration XHR: finished " );
  };
  xhr.onerror = function() {
    alert( "Request failed" );
  };
}
</script>


<style type="text/css">
button.user_ListQusers {
  text-align    : center;
  font-size     : 0.9rem;
  line-height   : 1em;
  color         : #ffffff;

  background    : #009900;
  padding       : 3px 5px;
  border        : 2px solid #009900;
  border-radius : 5%;
  box-shadow    : 2px 2px 3px #666666;
  margin        : 2px 3px;

  cursor        : pointer;
  transition    : .3s;
}
button.user_ListQusers:hover {
  color         : #009900;
  background    : #ffffff;
  box-shadow    : none;
}

button.user_SetQuser {
  text-align    : center;
  font-size     : 0.8rem;
  line-height   : 1.2em;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 10px;
  margin        : 2px 3px;
}
button.user_SetQuser:hover {
  background    : #00bb00;
}
button.user_SetQuser:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
</style>

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
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///
  }
}); // Not recommended ... orz

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>

Copy Input-History using REST API

Copy Input-History using REST API

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_ContentsOfDuties
  • span#user_Candidate_Form8
HTML/JavaScript (click to open)
<button type="button" class="user_SearchButton"
 onclick="user_getInputHistories(
           qbpms.form.get( 'q_ContentsOfDuties' ),
           'user_Candidate_Form8', 1883, 8
           )">Sample Search →</button>
<span id=  "user_Candidate_Form8" ></span>

<script>
///// Lists the copyable buttons in SPAN.
///// The buttons will support not only for Singleline-String but also Multiline-String.

//// Get List of ProcessInstances
function user_getInputHistories(
           strSearchWord, selectorForButtons, numWfappId, numDataItemId ) {
  let numLIMITSIZE     = 20;                         //// EDIT if you need ////

  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 = numWfappId;
      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 = numDataItemId;
        objCriteria.data[0].method = "contains";
        objCriteria.data[0].value  = strSearchWord;
      }
      objCriteria.fields = [];
      objCriteria.fields[0] = {};
      objCriteria.fields[0].type   = "string";
      objCriteria.fields[0].number = numDataItemId;
  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 );

          document.querySelector( "#" + selectorForButtons ).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";
                if ( objResponse.processInstances[i].data[numDataItemId].value === null ){
                  elNewButton.value     = "";
                  elNewButton.innerHTML = "∅"; // ←"&empty;" -- emptyset
                }else{
                  elNewButton.title     = objResponse.processInstances[i].data[numDataItemId].value;
                  elNewButton.value     = objResponse.processInstances[i].data[numDataItemId].value;
                  elNewButton.innerHTML = "❏ " + // ←"&#10063; " -- Lower right drop-shadowed white square
                    user_escapeHtml(
                      user_truncStr( objResponse.processInstances[i].data[numDataItemId].value )
                    );
                }
                elNewButton.onclick     = user_copyButtonValue;  /// avoid using event listeners
                elNewButton.classList.add("user_Appender");

            // "<a>p123</a>=<button>消耗品費</button>"
            document.querySelector( "#" + selectorForButtons )
                    .append( " ", elNewAnchor, ":", elNewButton );
          }
        }
        console.log( " Decoration XHR: finished " );
      };
      xhr.onerror = function() {
        alert("Request failed");
      };
}

//// Click BUTTON to copy to clipboard
// onclick = user_copyButtonValue
function     user_copyButtonValue() {
  let str = this.value; // this: "event.currentTarget"
  if( str !== "" ){
    navigator.clipboard.writeText( str );
    // https://developer.mozilla.org/docs/Web/API/Clipboard_API
    console.log( ">>> copied: '"+ user_truncStr(str) + "'" );
  }else{
    console.error( "   element.value is empty" );
  }
}
function user_truncStr( str ) {
  const numLength = 10;
  return str.length <= numLength ? str : (str.substr(0, numLength) + "…" );
}
function user_escapeHtml(str) {
  if( typeof str !== 'string' ) { console.error( " non-string escaping " ); return str; }
  return str.replace(/&/g, '&')  // ←"&amp;";
            .replace(/'/g, ''') // ←"&#x27;";
            .replace(/`/g, '`') // ←"&#x60;";
            .replace(/"/g, '"') // ←"&quot;";
            .replace(/</g, '<')   // ←"&lt;";
            .replace(/>/g, '>');  // ←"&gt;";
}
</script>

<style type="text/css">
button.user_Appender {
  text-align    : center;
  font-size     : 0.9rem;
  line-height   : 1.2;
  color         : #fff;

  background    : #050505;
  padding       : 3px 8px;
  border        : 0;
  border-radius : 10px;
  text-shadow   : 1px 1px 1px #000;
}
button.user_Appender:hover {
  background    : #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_SearchButton {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1em;
  color         : #ffffff;

  background    : #009900;
  padding       : 5px 20px;
  border        : 2px solid #009900;
  border-radius : 5%;

  display       : inline-block;
  cursor        : pointer;
  transition    : .3s;
  box-shadow    : 2px 2px 3px #666666;
}
button.user_SearchButton:hover {
  color         : #009900;
  background    : #ffffff;
  box-shadow    : none;
}
</style>

Clip to Browser Cookie

'Cookie Clip' is a note for yourself.
The sample below takes note of the "Registered Trade Name". Cross-Apps is also useful.
'クッキークリップ' は自分自身向けメモです。
以下のサンプルは "Registered Trade Name / 正式商号" をメモします。アプリ横断活用も便利です。

Online DEMO (public form)

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_RegisteredTradeName
  • span#user_CookieClips_RegisteredTradeName
HTML/JavaScript (click to open)
Check official name: 
<a href="https://www.houjin-bangou.nta.go.jp/" target="_Blank">法務省 法人番号公表サイト</a>,
<a href="https://www.nta.go.jp/publication/pamph/shohi/cross/touroku.pdf" target="_Blank">登録国外事業者名簿 PDF</a>
<br>
<button onclick='user_addCookieClip_RegisteredTradeName( "q_RegisteredTradeName" );'
 type='button' class='user_AddWordButton'>+ CookieClip</button>
<span id="user_CookieClips_RegisteredTradeName"></span>

<script>
/// 'Cookie Clip' is a note for yourself.
/// The sample below takes note of the "Registered Trade Name". Cross-Apps is also useful.
/// 'クッキークリップ' は自分自身向けメモです。
/// 以下のサンプルは "Registered Trade Name / 正式商号" をメモします。アプリ横断活用も便利です。

qbpms.form.on( 'ready', user_loadCookieClips_RegisteredTradeName );     //// EDIT ////

// ▼▼▼ user_loadCookieClips_XXX ▼▼▼
function  user_loadCookieClips_RegisteredTradeName(){                   //// EDIT ////
  let strCookieName        = "user_CookieRTN";                          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_RegisteredTradeName";   //// EDIT ////

  let strClipsTSV  = user_getCookieValue( strCookieName );
  if( strClipsTSV !== "" ){
    user_setCookieValue( strCookieName, strClipsTSV );
    user_showClipButtons( strClipsTSV, selectorButtonListed );
  }
}
// ▼▼▼ onclick: user_addCookieClip_XXX ▼▼▼ 
function user_addCookieClip_RegisteredTradeName( qStrField ){           //// EDIT ////
  let strCookieName        = "user_CookieRTN";                          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_RegisteredTradeName";   //// EDIT ////

  let strTarget   = qbpms.form.get( qStrField );
  if( strTarget   === null ){ return; } // ReadOnly source
  if( strTarget   === ""   ){ return; } // ReadWrite source
  strTarget = strTarget.trim().replace('\t',' ');

  let strClipsTSV = user_getCookieValue( strCookieName );
  let strNewTSV   = "";
  if( strClipsTSV === "" ){
      strNewTSV   = strTarget;
  }else{
      strNewTSV   = strClipsTSV + "\t" + strTarget;
  }
  user_setCookieValue( strCookieName, strNewTSV );
  user_showClipButtons( strNewTSV, selectorButtonListed );
}
// ▼▼▼ onclick: user_deleteCookieClip_XXX ▼▼▼ 
function user_deleteCookieClip_RegisteredTradeName(){                   //// EDIT ////
  let strCookieName        = "user_CookieRTN";                          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_RegisteredTradeName";   //// EDIT ////

  let strTarget   = this.value;
  let strClipsTSV = user_getCookieValue( strCookieName );
  let arrClipsTSV = strClipsTSV.split( '\t' );
  let numIndex    = arrClipsTSV.indexOf( strTarget );
  console.log( " delete[" + numIndex + "]: " + strTarget );
  if( numIndex < 0 ){ console.log( " search element error " ); return; }
  arrClipsTSV.splice( numIndex, 1 );

  user_setCookieValue( strCookieName, arrClipsTSV.join( '\t' ) );
  user_showClipButtons( arrClipsTSV.join( '\t' ), selectorButtonListed );
}
// ▼▼▼ onclick: user_moveLeftCookieClip_XXX ▼▼▼ 
function user_moveLeftCookieClip_RegisteredTradeName(){                 //// EDIT ////
  let strCookieName        = "user_CookieRTN";                          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_RegisteredTradeName";   //// EDIT ////

  let strTarget   = this.value;
  let strClipsTSV = user_getCookieValue( strCookieName );
  let arrClipsTSV = strClipsTSV.split( '\t' );
  let numIndex    = arrClipsTSV.indexOf( strTarget );
  if( numIndex < 0 ){
    console.log( " search element error " ); return;
  }else if( numIndex === 0 ){ // Leftmost
    console.log( " move [" + numIndex + "] to [" + (arrClipsTSV.length-1) + "} :"  + strTarget );
    arrClipsTSV.shift();
    arrClipsTSV.push( strTarget );
  }else if( numIndex > 0 ){
    console.log( " move [" + numIndex + "] to [" + (numIndex-1) + "} :"  + strTarget );
    arrClipsTSV.splice( numIndex, 1 );
    arrClipsTSV.splice( numIndex-1, 0, strTarget );
  }

  user_setCookieValue( strCookieName, arrClipsTSV.join( '\t' ) );
  user_showClipButtons( arrClipsTSV.join( '\t' ), selectorButtonListed );
}
// ▽▽▽ onclick: user_copyButtonValueToClipboard ▽▽▽
function user_copyButtonValueToClipboard(){
  let str = this.value;
  if( str !== "" ){
    navigator.clipboard.writeText( str );
    // https://developer.mozilla.org/docs/Web/API/Clipboard_API
  }
}

// ▼▼ user_showClipButtons ▼▼ 
function user_showClipButtons( strClipsTSV, selectorButtonListed ){
  document.querySelector( selectorButtonListed ).innerHTML = ''; // remove all children
  let arrClips = strClipsTSV.split('\t');
  for( let i = 0; i < arrClips.length; i++ ){
    user_addClipButton( arrClips[i], selectorButtonListed );
  }
}
// ▼▼ user_setCookieValue ▼▼ 
function user_setCookieValue( strCookieName, strValue ){
  console.log( strCookieName + "=" + strValue );
  let strNewCookie = strCookieName + "=" +
                     encodeURIComponent( strValue ) + "; max-age=" + (60*60*24*100); // 100 days
  if( strNewCookie.length > 4000 ){ console.log( " too much values " ); return; }
  document.cookie  = strNewCookie;
}
// ▼▼ user_getCookieValue ▼▼ 
function user_getCookieValue( strCookieName ){
  let regValue = new RegExp( "(?:(?:^|.*;\s*)" + strCookieName + "\s*\=\s*([^;]*).*$)|^.*$" );
  let strValue = document.cookie.replace( regValue, '$1');
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Backreferences
  //   (?:x) --- Non-capturing group: Matches "x" but does not remember the match.
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
  //   \s --- a single white space character, including space, tab, form feed, line feed, and other Unicode spaces.
  strValue = decodeURIComponent( strValue );
  let arrValues = strValue.split('\t');
  console.log( strCookieName + "[" + arrValues.length + "]: " + strValue );
  return strValue;
}
// ▼ user_addClipButton ▼ 
function user_addClipButton( strClip, selectorButtonListed ){
  if( strClip === ""   ){ return; }
  let elNewSpan  = document.createElement( "span" );
      elNewSpan.style.whiteSpace = "nowrap";
  let elNewButton = document.createElement( "button" );
      elNewButton.type      = "button";
      elNewButton.title     = "copy to clipboard"; // as tooltip
      elNewButton.ariaLabel = "Copy";
      elNewButton.value     = strClip;
      elNewButton.innerHTML = "❏ " + strClip; // "&#10063; " -- Lower right drop-shadowed white square
      elNewButton.onclick   = user_copyButtonValueToClipboard;
      elNewButton.classList.add( "user_CopyWordButton" );
  let elNewDelButton = document.createElement( "button" );
      elNewDelButton.type      = "button";
      elNewDelButton.title     = "delete button"; // as tooltip
      elNewDelButton.ariaLabel = "Delete";
      elNewDelButton.value     = strClip;
      elNewDelButton.innerHTML = "⌫"; // "&#9003;" -- DELETE mark
      elNewDelButton.onclick   = user_deleteCookieClip_RegisteredTradeName;   //// EDIT ////
      elNewDelButton.classList.add( "user_DeleteWordButton" );
  let elNewMoveButton = document.createElement( "button" );
      elNewMoveButton.type      = "button";
      elNewMoveButton.title     = "move to left"; // as tooltip
      elNewMoveButton.ariaLabel = "Move";
      elNewMoveButton.value     = strClip;
      elNewMoveButton.innerHTML = "⇆ "; // "&#8646; " -- Leftwards arrow over rightwards arrow
      elNewMoveButton.onclick   = user_moveLeftCookieClip_RegisteredTradeName;   //// EDIT ////
      elNewMoveButton.classList.add( "user_MoveButton" );
  elNewSpan.append( elNewMoveButton, elNewButton, elNewDelButton );
  document.querySelector( selectorButtonListed ).append( " ", elNewSpan );
}
</script>
<style type="text/css">
button.user_AddWordButton {
  font-size     : 0.9rem;
  line-height   : 1rem;
  color         : #ffffff;

  background    : #009900;
  padding       : 3px 5px;
  border        : 2px solid #009900;
  border-radius : 5%;
  box-shadow    : 2px 2px 3px #666666;
  margin        : 2px 3px;

  cursor        : pointer;
  transition    : .3s;
}
button.user_AddWordButton:hover {
  color         : #009900;
  background    : #ffffff;
  box-shadow    : none;
}

button.user_CopyWordButton {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 0 0 0;
  margin        : 2px 0 2px 0;
}
button.user_CopyWordButton:hover {
  background    : #00bb00;
}
button.user_CopyWordButton:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_DeleteWordButton {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 10px 10px 0;
  margin        : 2px 3px 2px 1px;
}
button.user_DeleteWordButton:hover {
  background    : #00bb00;
}
button.user_DeleteWordButton:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_MoveButton {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 10px 0 0 10px;
  margin        : 2px 1px 2px 3px;
}
button.user_MoveButton:hover {
  background    : #00bb00;
}
button.user_MoveButton:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
</style>

Online DEMO (public form)

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_FilterPhrase
  • span#user_CookieClips_FilterPhrase
HTML/JavaScript (click to open)
<button onclick='user_addCookieClip_FilterPhrase( "q_FilterPhrase" );'
 type='button' class='user_AddWordButton'>+ CookieClip</button>
<span id="user_CookieClips_FilterPhrase"></span>

<script>
qbpms.form.on( 'ready', user_loadCookieClips_FilterPhrase );     //// EDIT ////

// ▼▼▼ user_loadCookieClips_XXX ▼▼▼
function  user_loadCookieClips_FilterPhrase(){                   //// EDIT ////
  let strCookieName        = "user_CookieFilterPhrase";          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_FilterPhrase";   //// EDIT ////

  let strClipsTSV  = user_getCookieValue( strCookieName );
  if( strClipsTSV !== "" ){
    user_setCookieValue( strCookieName, strClipsTSV );
    user_showClipButtons( strClipsTSV, selectorButtonListed );
  }
}
// ▼▼▼ onclick: user_addCookieClip_XXX ▼▼▼ 
function user_addCookieClip_FilterPhrase( qStrField ){           //// EDIT ////
  let strCookieName        = "user_CookieFilterPhrase";          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_FilterPhrase";   //// EDIT ////

  let strTarget   = qbpms.form.get( qStrField );
  if( strTarget   === null ){ return; } // ReadOnly source
  if( strTarget   === ""   ){ return; } // ReadWrite source
  strTarget = strTarget.trim().replace('\t',' ');

  let strClipsTSV = user_getCookieValue( strCookieName );
  let strNewTSV   = "";
  if( strClipsTSV === "" ){
      strNewTSV   = strTarget;
  }else{
      strNewTSV   = strClipsTSV + "\t" + strTarget;
  }
  user_setCookieValue( strCookieName, strNewTSV );
  user_showClipButtons( strNewTSV, selectorButtonListed );
}
// ▼▼▼ onclick: user_deleteCookieClip_XXX ▼▼▼ 
function user_deleteCookieClip_FilterPhrase(){                   //// EDIT ////
  let strCookieName        = "user_CookieFilterPhrase";          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_FilterPhrase";   //// EDIT ////

  let strTarget   = this.value;
  let strClipsTSV = user_getCookieValue( strCookieName );
  let arrClipsTSV = strClipsTSV.split( '\t' );
  let numIndex    = arrClipsTSV.indexOf( strTarget );
  console.log( " delete[" + numIndex + "]: " + strTarget );
  if( numIndex < 0 ){ console.log( " search element error " ); return; }
  arrClipsTSV.splice( numIndex, 1 );

  user_setCookieValue( strCookieName, arrClipsTSV.join( '\t' ) );
  user_showClipButtons( arrClipsTSV.join( '\t' ), selectorButtonListed );
}
// ▼▼▼ onclick: user_moveLeftCookieClip_XXX ▼▼▼ 
function user_moveLeftCookieClip_FilterPhrase(){                 //// EDIT ////
  let strCookieName        = "user_CookieFilterPhrase";          //// EDIT //// (RegSymbols unavailable)
  let selectorButtonListed = "#user_CookieClips_FilterPhrase";   //// EDIT ////

  let strTarget   = this.value;
  let strClipsTSV = user_getCookieValue( strCookieName );
  let arrClipsTSV = strClipsTSV.split( '\t' );
  let numIndex    = arrClipsTSV.indexOf( strTarget );
  if( numIndex < 0 ){
    console.log( " search element error " ); return;
  }else if( numIndex === 0 ){ // Leftmost
    console.log( " move [" + numIndex + "] to [" + (arrClipsTSV.length-1) + "} :"  + strTarget );
    arrClipsTSV.shift();
    arrClipsTSV.push( strTarget );
  }else if( numIndex > 0 ){
    console.log( " move [" + numIndex + "] to [" + (numIndex-1) + "} :"  + strTarget );
    arrClipsTSV.splice( numIndex, 1 );
    arrClipsTSV.splice( numIndex-1, 0, strTarget );
  }

  user_setCookieValue( strCookieName, arrClipsTSV.join( '\t' ) );
  user_showClipButtons( arrClipsTSV.join( '\t' ), selectorButtonListed );
}
// ▽▽▽ onclick: user_copyButtonValueToClipboard ▽▽▽
function user_copyButtonValueToClipboard(){
  let str = this.value;
  if( str !== "" ){
    navigator.clipboard.writeText( str );
    // https://developer.mozilla.org/docs/Web/API/Clipboard_API
  }
}

// ▼▼ user_showClipButtons ▼▼ 
function user_showClipButtons( strClipsTSV, selectorButtonListed ){
  document.querySelector( selectorButtonListed ).innerHTML = ''; // remove all children
  let arrClips = strClipsTSV.split('\t');
  for( let i = 0; i < arrClips.length; i++ ){
    user_addClipButton( arrClips[i], selectorButtonListed );
  }
}
// ▼▼ user_setCookieValue ▼▼ 
function user_setCookieValue( strCookieName, strValue ){
  console.log( "SET " + strCookieName + "=" + strValue );
  let strNewCookie = strCookieName + "=" +
                     encodeURIComponent( strValue ) + "; max-age=" + (60*60*24*100); // 100 days
  if( strNewCookie.length > 4000 ){ console.log( " too much values " ); return; }
  document.cookie  = strNewCookie;
}
// ▼▼ user_getCookieValue ▼▼ 
function user_getCookieValue( strCookieName ){
  let regValue = new RegExp( "(?:(?:^|.*;\s*)" + strCookieName + "\s*\=\s*([^;]*).*$)|^.*$" );
  let strValue = document.cookie.replace( regValue, '$1');
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Backreferences
  //   (?:x) --- Non-capturing group: Matches "x" but does not remember the match.
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
  //   \s --- a single white space character, including space, tab, form feed, line feed, and other Unicode spaces.
  strValue = decodeURIComponent( strValue );
  let arrValues = strValue.split('\t');
  console.log( "GET " + strCookieName + "[" + arrValues.length + "]: " + strValue );
  return strValue;
}
// ▼ user_addClipButton ▼ (EDIT XXX)
function user_addClipButton( strClip, selectorButtonListed ){
  if( strClip === ""   ){ return; }
  let elNewSpan  = document.createElement( "span" );
      elNewSpan.style.whiteSpace = "nowrap";
  let elNewButton = document.createElement( "button" );
      elNewButton.type      = "button";
      elNewButton.title     = "copy to clipboard"; // as tooltip
      elNewButton.ariaLabel = "Copy";
      elNewButton.value     = strClip;
      elNewButton.innerHTML = "❏ " + strClip; // "&#10063; " -- Lower right drop-shadowed white square
      elNewButton.onclick   = user_copyButtonValueToClipboard;
      elNewButton.classList.add( "user_CopyWordButton" );
  let elNewDelButton = document.createElement( "button" );
      elNewDelButton.type      = "button";
      elNewDelButton.title     = "delete button"; // as tooltip
      elNewDelButton.ariaLabel = "Delete";
      elNewDelButton.value     = strClip;
      elNewDelButton.innerHTML = "⌫"; // "&#9003;" -- DELETE mark
      elNewDelButton.onclick   = user_deleteCookieClip_FilterPhrase;      //// EDIT XXX ////
      elNewDelButton.classList.add( "user_DeleteWordButton" );
  let elNewMoveButton = document.createElement( "button" );
      elNewMoveButton.type      = "button";
      elNewMoveButton.title     = "move to left"; // as tooltip
      elNewMoveButton.ariaLabel = "Move";
      elNewMoveButton.value     = strClip;
      elNewMoveButton.innerHTML = "⇆ "; // "&#8646; " -- Leftwards arrow over rightwards arrow
      elNewMoveButton.onclick   = user_moveLeftCookieClip_FilterPhrase;   //// EDIT XXX ////
      elNewMoveButton.classList.add( "user_MoveButton" );
  elNewSpan.append( elNewMoveButton, elNewButton, elNewDelButton );
  document.querySelector( selectorButtonListed ).append( " ", elNewSpan );
}
</script>
<style type="text/css">
button.user_AddWordButton {
  font-size     : 0.9rem;
  line-height   : 1rem;
  color         : #ffffff;

  background    : #009900;
  padding       : 3px 5px;
  border        : 2px solid #009900;
  border-radius : 5%;
  box-shadow    : 2px 2px 3px #666666;
  margin        : 2px 3px;

  cursor        : pointer;
  transition    : .3s;
}
button.user_AddWordButton:hover {
  color         : #009900;
  background    : #ffffff;
  box-shadow    : none;
}

button.user_CopyWordButton {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 0 0 0;
  margin        : 2px 0 2px 0;
}
button.user_CopyWordButton:hover {
  background    : #00bb00;
}
button.user_CopyWordButton:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_DeleteWordButton {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 10px 10px 0;
  margin        : 2px 3px 2px 1px;
}
button.user_DeleteWordButton:hover {
  background    : #00bb00;
}
button.user_DeleteWordButton:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_MoveButton {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 10px 0 0 10px;
  margin        : 2px 1px 2px 3px;
}
button.user_MoveButton:hover {
  background    : #00bb00;
}
button.user_MoveButton:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
</style>

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_AddressWithZipCode
  • → STRING (STRING_TEXTFIELD): q_ZIPCode
  • → STRING (STRING_TEXTFIELD): q_Address
  • → STRING (STRING_TEXTFIELD): q_BizName
HTML/JavaScript (click to open)
<button onclick='user_splitAddressWithZipCode( "q_AddressWithZipCode",
                                "q_ZIPCode", "q_Address", "q_BizName" );'
 type='button' >Copy to below</button>
<script>
function user_splitAddressWithZipCode( qStrFieldAWZC, qStrFieldZ, qStrFieldA, qStrFieldBiz ){
  let qSelectAWZC = qbpms.form.get( qStrFieldAWZC );
  if( qSelectAWZC.length === 0 ){ console.log( " not selected " ); return; }
  let strAWZC = qSelectAWZC[0].display;
  let arrAWZC = strAWZC.split( " " );
  qbpms.form.set( qStrFieldZ,   arrAWZC[0] );
  qbpms.form.set( qStrFieldA,   arrAWZC[1] );
  qbpms.form.set( qStrFieldBiz, (arrAWZC[2] ?? "") );
} 
</script>
CookieClip (simplified)

Online DEMO (public form), Download Sample (Workflow App)

Input / Output

  • ← STRING (STRING_TEXTFIELD): q_ApprovalTitle
    • span#user_ApprovalTitle
  • ← STRING (STRING_TEXTAREA): q_ApprovalOverview
    • span#user_ApprovalOverview
  • ← STRING (STRING_TEXTAREA): q_ApprovalDescription
    • span#user_ApprovalDescription
HTML/JavaScript (click to open)
<button onclick='user_appendClip( "user_ApprovalTitle", "q_ApprovalTitle" );'
 type='button' class='user_ButtonAppend'>+ CookieClip ⇒</button>
<span id="user_ApprovalTitle"></span>
HTML/JavaScript (click to open)
<button onclick='user_appendClip( "user_ApprovalOverview", "q_ApprovalOverview" );'
 type='button' class='user_ButtonAppend'>+ CookieClip ⇒</button>
<span id="user_ApprovalOverview"></span>
HTML/JavaScript (click to open)
<button onclick='user_appendClip( "user_ApprovalDescription", "q_ApprovalDescription" );'
 type='button' class='user_ButtonAppend'>+ CookieClip ⇒</button>
<span id="user_ApprovalDescription"></span>
HTML/JavaScript (click to open)
<h4 style="font-size:13px;background-color:#198000;color:#ffffff;padding:3px;margin-bottom:3px;">
 Approval Flows / 申請承認</h4>
'Cookie Clip' is a note for yourself. / 'CookieClip' は自分自身向けメモです。
<p style="text-align:right">
 <button onclick="user_deleteAllClips()" type='button'>🗑 Clear ALL Clips</button>
</p>

<script>
// <!-- "&#128465;" --- Wastebasket -->
// <!-- Fundamental Methods & Styles in "Guide Panel" or "Title" etc -->

// ★★★★★★★★★★▽ EDIT HERE ▽★★★★★★★★★★
// "CookieKey" will be generated by "user_arrSelectorIds[i]".
const user_arrSelectorIds = [
  "user_ApprovalTitle",         // ← <span id="user_ApprovalTitle"></span>
  "user_ApprovalOverview",      // ← <span id="user_ApprovalOverview"></span>
  "user_ApprovalDescription" ]; // ← <span id="user_ApprovalDescription"></span>
// ★★★★★★★★★★△ EDIT HERE △★★★★★★★★★★

///▼▼▼ user_loadAllClips() ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
qbpms.form.on( 'ready', user_loadClips );
function user_loadClips() {
  for( let i = 0; i < user_arrSelectorIds.length; i++ ) { // Selector: SpanID in which Button Listed
    let strClipTsv = user_getCookie( user_arrSelectorIds[i] );
    if( strClipTsv === "" ){ console.log( " = no cookie: '" + user_arrSelectorIds[i] + "'" ); continue; }
    user_setCookie( user_arrSelectorIds[i], strClipTsv ); // Extend expiration date
    user_listClips( user_arrSelectorIds[i], strClipTsv );
  }
}
///▼▼▼ user_deleteAllClips() ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
function user_deleteAllClips() {
  if( ! confirm( "Clear All Cookies for Clips?" ) ){ return; }
  console.log( " <<< Delete All Clips " );
  for( let i = 0; i < user_arrSelectorIds.length; i++ ) {
    user_deleteCookie( user_arrSelectorIds[i] );
    user_listClips   ( user_arrSelectorIds[i], "" );
  }
}
///▼▼▼ user_appendClip(str,str) ▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼
function user_appendClip( selectorId, qStrField ){
  let strTarget  = qbpms.form.get( qStrField );
  if( strTarget  === null ){ return; } // ReadOnly source
  if( strTarget  === ""   ){ return; } // ReadWrite source
  strTarget = strTarget.trim().replace( '\t', ' ' );
  let strClipTsv    = user_getCookie( selectorId );
  let strClipTsvNew = "";
  if( strClipTsv    === "" ){
      strClipTsvNew = strTarget;
  }else{
    let arrClipTsv  = strClipTsv.split('\t');
    if( arrClipTsv.includes( strTarget ) ){
      console.log( " already exists " );
      return; // Duplicate entries are not allowed.
    }else{
      strClipTsvNew = strClipTsv + "\t" + strTarget;
    }
  }
  user_setCookie( selectorId, strClipTsvNew );
  user_listClips( selectorId, strClipTsvNew );
}

///▼ user_getCookie(str) ▼▼▼▼▼
function user_getCookie( strCookieKey ) { // Split cookie string and get all individual "key=value" pairs in an array
  let arrCookie = document.cookie.split(";");   // eg; "...; q_KeyA=ValueA; q_KeyB=ValueB;..."
  for( let i = 0; i < arrCookie.length; i++ ) { // Loop through the array elements
    if( ! arrCookie[i].includes('=') ){ console.log( " - getCookie: noise data included" ); continue; }
    let arrCookiePair = arrCookie[i].split("=");
    if( strCookieKey === arrCookiePair[0].trim() ) { // Removing whitespace at the beginning of the cookie name
      console.log( " < getCookie: "+ strCookieKey );
      //console.log( " < getCookie: "+ strCookieKey + "=" + arrCookiePair[1] ); // for Debug
      return decodeURIComponent( arrCookiePair[1] );
    }
  }
  return ''; // Return empty
}
///▼ user_setCookie(str,str) ▼▼▼▼▼
function user_setCookie( strCookieKey, strCookieValue ) {
  let strNewCookiePair = strCookieKey + "=" + encodeURIComponent( strCookieValue );
      strNewCookiePair += "; max-age=" + (60*60*24*366); // 366 days
  console.log( " > setCookie: "+ strCookieKey );
  // console.log( " > setCookie: "+ strNewCookiePair ); // for Debug
  document.cookie = strNewCookiePair;
}
///▼ user_deleteCookie(str) ▼▼▼▼▼
function user_deleteCookie( strCookieKey ) {
  let strNewCookiePair = strCookieKey + "=; max-age=-1";
  document.cookie = strNewCookiePair;
}
///▼▼ user_listClips(str,str) ▼▼▼▼▼▼▼▼▼▼
function user_listClips( selectorId, strClipTsv ) { // List Buttons. "selectorId" is also "CookieKey".
  console.log( "   Element '#" + selectorId + "' rewritten" );
  if( document.querySelector( "#" + selectorId ) === null ){ console.error( "no '#" + selectorId + "'" ); return; }
  document.querySelector( "#" + selectorId ).innerHTML = ''; // remove all children
  if( strClipTsv === "" ){ return; }
  let arrClips = strClipTsv.split('\t');
  for( let i = 0; i < arrClips.length; i++ ){
    user_createButton( selectorId, arrClips[i] );
  }
}
///▼ user_createButton(str,str) ▼▼▼▼▼▼▼▼▼▼
function user_createButton( selectorId, strClip ) { // Generate Button in "selectorId".
  if( strClip === ""   ){ return; }
  let elNewSpan  = document.createElement( "span" );
      elNewSpan.style.whiteSpace = "nowrap";
  let elNewButton = document.createElement( "button" );
      elNewButton.type      = "button";
      elNewButton.title     = strClip; // for previewing as tooltip
      elNewButton.value     = strClip;
      elNewButton.innerHTML = "❏ " + // "&#10063; " -- Lower right drop-shadowed white square
                              user_truncateStr( strClip );
      elNewButton.onclick   = user_copyClip;
      elNewButton.classList.add( "user_ButtonCopy" );
  let elNewButtonDel = document.createElement( "button" );
      elNewButtonDel.type      = "button";
      elNewButtonDel.title     = "delete Clip"; // as tooltip
      elNewButtonDel.value     = strClip;
      elNewButtonDel.innerHTML = "⌫"; // "&#9003;" -- DELETE mark
      elNewButtonDel.onclick   = user_deleteClip;
      elNewButtonDel.classList.add( "user_ButtonDelete" );
  let elNewButtonShift = document.createElement( "button" );
      elNewButtonShift.type      = "button";
      elNewButtonShift.title     = "move to left"; // as tooltip
      elNewButtonShift.value     = strClip;
      elNewButtonShift.innerHTML = "⇆ "; // "&#8646; " -- Leftwards arrow over rightwards arrow
      elNewButtonShift.onclick   = user_shiftClip;
      elNewButtonShift.classList.add( "user_ButtonShift" );
  elNewSpan.append( elNewButtonShift, elNewButton, elNewButtonDel );
  document.querySelector( "#" + selectorId ).append( " ", elNewSpan );
}
///▽ user_copyClip() ▽▽▽▽▽
function user_copyClip() {
  let str = this.value;  // this: "event.currentTarget"
  if( str !== "" ){
    navigator.clipboard.writeText( str );
    // https://developer.mozilla.org/docs/Web/API/Clipboard_API
    console.log( ">>> copied: '"+ user_truncateStr(str) + "'" );
  }
}
///▽ user_deleteClip() ▽▽▽▽▽
function user_deleteClip() {
  let selectorId = this.parentElement.parentElement.id;
  console.log( "=== Delete button in " + selectorId + " is clicked ===" );
  let strTarget  = this.value;
  strTarget      = strTarget.trim();
  let strClipTsv = user_getCookie( selectorId ); // selectorId is also CookieKey
  let arrClipTsv = strClipTsv.split('\t');
  let numIndex   = arrClipTsv.indexOf( strTarget );
  if( numIndex < 0 ){ console.error( " search Value error in DeleteClip " ); return; }
  console.log( "   [" + numIndex + "]: " + strTarget ); // no "user_truncateStr()" for operation mistakes
  arrClipTsv.splice( numIndex, 1 );
  user_setCookie( selectorId, arrClipTsv.join('\t') );
  user_listClips( selectorId, arrClipTsv.join('\t') );
}
///▽ user_shiftClip() ▽▽▽▽▽
function user_shiftClip() {
   // span-selectorId(CookieKey) > span-nowrap > button-clip
  let selectorId = this.parentElement.parentElement.id;
  console.log( "=== Shift button in " + selectorId + " is clicked ===" );
  let strTarget  = this.value;
  strTarget      = strTarget.trim();
  let strClipTsv = user_getCookie( selectorId ); // selectorId is also CookieKey
  let arrClipTsv = strClipTsv.split('\t');
  let numIndex   = arrClipTsv.indexOf( strTarget );
  if( numIndex < 0 ){
    console.error( " search Value error in ShiftClip " ); return;
  }else if( numIndex === 0 ){ // Leftmost
    console.log( "   [" + numIndex + "] to [" + (arrClipTsv.length-1) + "]: " + user_truncateStr(strTarget) );
    arrClipTsv.shift();
    arrClipTsv.push( strTarget );
  }else if( numIndex > 0 ){
    console.log( "   [" + numIndex + "] to [" + (numIndex-1) + "]: " + user_truncateStr(strTarget) );
    arrClipTsv.splice( numIndex, 1 );
    arrClipTsv.splice( numIndex-1, 0, strTarget );
  }
  user_setCookie( selectorId, arrClipTsv.join('\t') );
  user_listClips( selectorId, arrClipTsv.join('\t') );
}
///▽ user_truncateStr(str) ▽▽▽▽▽
/*
function user_truncateStr( str ) {
  const numLength = 10;
  return str.length <= numLength ? str : (str.substr(0, numLength) + "…" );
  // "&hellip;" --- Horizontal Ellipsis / 水平省略記号
}
*/
function user_truncateStr( str ) {
  const numLength = 10;
  return str.length <= numLength ?
          user_escapeHtml( str ) : (user_escapeHtml( str ).substr(0, numLength) + "…" );
  // "&hellip;" --- Horizontal Ellipsis / 水平省略記号
}
function user_escapeHtml(str) {
  if( typeof str !== 'string' ) { return str; }
  return str.replace( /[&'`"<>]/g, function(match) {
    return {
      '&': '&',  // "&amp;"
      "'": ''', // "&#x27;"
      '`': '`', // "&#x60;"
      '"': '"', // "&quot;"
      '<': '<',   // "&lt;"
      '>': '>',   // "&gt;"
    }[match]
  });
}
</script>
<style>
button.user_ButtonCopy {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 0 0 0;
  margin        : 2px 0 2px 0;
}
button.user_ButtonCopy:hover {
  background    : #00bb00;
}
button.user_ButtonCopy:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_ButtonDelete {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 10px 10px 0;
  margin        : 2px 3px 2px 1px;
}
button.user_ButtonDelete:hover {
  background    : #00bb00;
}
button.user_ButtonDelete:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_ButtonShift {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 10px 0 0 10px;
  margin        : 2px 1px 2px 3px;
}
button.user_ButtonShift:hover {
  background    : #00bb00;
}
button.user_ButtonShift:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_ButtonAppend {
  font-size     : 0.9rem;
  line-height   : 1rem;
  color         : #ffffff;

  background    : #009900;
  padding       : 3px 5px;
  border        : 2px solid #009900;
  border-radius : 5%;
  box-shadow    : 2px 2px 3px #666666;
  margin        : 2px 3px;

  cursor        : pointer;
  transition    : .3s;
}
button.user_ButtonAppend:hover {
  color         : #009900;
  background    : #ffffff;
  box-shadow    : none;
}
</style>

Clip to Browser Storage

Clip to Browser Storage: "StorageClip" is a note for yourself. Improve your business with copy paste. You can save any sentences to the 'localStorage'. It also supports line-feed codes and tab codes.

Online DEMO (public form)
Workflow App example (qar file)
HTML/JavaScript (text file)

Input / Output

  • ← STRING (TITLE): title
    • span#user_ReportTitle (append)
  • ← STRING (STRING_TEXTAREA): q_RegionMaster
    • span#user_RegionMaster (append)
HTML/JavaScript (click to open)
<script>
////// Clip to Browser Storage
// "StorageClip" is a note for yourself. Improve your business with copy paste.
// You can save any sentences to the 'localStorage'. It also supports line-feed codes and tab codes.
//
// 1.
// Set this code in the Description of dataitem ("Guide Panel" or "Title") that is loaded in all Steps.
// このコード全体を[説明]部に貼り付けます。@全工程で読み込まれるデータ項目("ガイドパネル" や "件名")
// 2.
// To display the Clip button, add the button tag code in the dataitem (such as "String" or "Numeric").
// Clipボタンを表示させたいデータ項目("文字型" や "数値" など)に、buttonタグコードを記述します。
//
// ★★★★★★★★★★▽ (1) EDIT HERE ▽★★★★★★★★★★
// "StorageKey" will be generated with the same name as "user_arrSelectorIds[i]".
const user_arrSelectorIds = [
  "user_ReportTitle",
  "user_RegionMaster"
]; // "user_●SelectorId●",
// ★★★★★★★★★★△ (1) EDIT HERE △★★★★★★★★★★
//
// ★★★★★★★★★★▽ (2) ADD HTML  ▽★★★★★★★★★★
/*
// Add this code to every data item you want to display "StorageClip".
<button type="button" class="user_StorageClip"
 onclick="user_appendClip(
            'user_●SelectorId●',
            qbpms.form.get( 'q_◆FieldName◆' )
          )"><span class="material-icons">add_circle</span> Storage Clip</button>
<span id="user_●SelectorId●"></span>

         // qbpms.form.get( 'q_◆Numeric◆' ).toString()
         // qbpms.form.get( 'q_◆Select◆' )[0].value -- only to paste elsewhere
         // qbpms.form.get( 'q_◆Date◆' ).toLocaleString('sv-SE').slice(0, 10)
         // qbpms.form.get( 'q_◆Datetime◆' ).toLocaleString('sv-SE').slice(0, 16)
         // qbpms.form.get( 'q_◆Quser◆' ).email
         // qbpms.form.get( 'q_◆Qorg◆' ).name

// If you need to delete all clips, add this code.
<button type="button"
 onclick="user_deleteAllClips()">Clear All Clips</button>
*/
// ★★★★★★★★★★△ (2) ADD HTML △★★★★★★★★★★
//
// - ▼ Web Storage API
// - https://developer.mozilla.org/docs/Web/API/Web_Storage_API
//     - ▼ Window.localStorage
//     - https://developer.mozilla.org/docs/Web/API/Window/localStorage



///▽▽▽▽ user_loadAllClips() ▽▽▽▽
qbpms.form.on( 'ready', user_loadAllClips );
function user_loadAllClips() {
  for( let i = 0; i < user_arrSelectorIds.length; i++ ) { // Selector: SpanID in which Button Listed
    let jsonArrClips   = localStorage.getItem( user_arrSelectorIds[i] );
    if( jsonArrClips === null ){ console.log( " = no data, key: '" + user_arrSelectorIds[i] + "'" ); continue; }
    user_listClips( user_arrSelectorIds[i], JSON.parse( jsonArrClips ) );
  }
}
///▽▽▽▽ user_deleteAllClips() ▽▽▽▽
function user_deleteAllClips() {
  if( ! confirm( "Clear All localStorage for Clips?" ) ){ return; }
  console.log( " <<< Delete All Clips " );
  for( let i = 0; i < user_arrSelectorIds.length; i++ ) {
    localStorage.removeItem( user_arrSelectorIds[i] );
    user_listClips( user_arrSelectorIds[i], [] );
    console.log( " <<< deleted: '"+ user_arrSelectorIds[i] + "'" );
  } // see also "localStorage.clear()"
}
///▽▽▽▽ user_appendClip(str,str) ▽▽▽▽
function user_appendClip( selectorId, strClip ){
  if( strClip      === null ){ return; } // String ReadOnly source
  if( strClip      === ""   ){ return; } // ReadWrite source
  let jsonArrClips   = localStorage.getItem( selectorId );
  let arrClips       = [];
  if( jsonArrClips === null ){
    console.log( " = no data for the key: '" + selectorId + "'" );
  }else{
    arrClips         = JSON.parse( jsonArrClips );
  }
  if( arrClips.includes( strClip ) ){
    console.log( " already exists " );
    return; // Duplicate entries are not allowed.
  }else{
    arrClips.push( strClip );
  }
  user_listClips( selectorId, arrClips );
  localStorage.setItem( selectorId, JSON.stringify( arrClips ) );
  // "StorageKey" is generated with the same name as "selectorId".
}

///▼▼ user_listClips(str,arrStr) ▼▼
function user_listClips( selectorId, arrClips ) { // List all Clips as Buttons in the "selectorId" area.
  console.log( "   Element '#" + selectorId + "' rewritten" );
  if( document.querySelector( "#" + selectorId ) === null ){ console.error( "no '#" + selectorId + "'" ); return; }
  document.querySelector( "#" + selectorId ).textContent = ''; // remove all children
  if( arrClips.length === 0 ){ return; }
  for( let i = 0; i < arrClips.length; i++ ){
    user_createButton( selectorId, arrClips[i] );
  }
}
///▼ user_createButton(str,str) ▼
function user_createButton( selectorId, strClip ) { // Generate Button in the "selectorId" area.
  if( strClip   === "" ){ return; }

  let elButtons          = document.createElement( "span" );
  elButtons.style.whiteSpace = "nowrap"; // Prevention of line feed code insertion

  let elButtonCopy       = document.createElement( "button" );
  elButtonCopy.type      = "button";
  elButtonCopy.title     = strClip; // for previewing as tooltip
  elButtonCopy.value     = strClip;
  elButtonCopy.onclick   = user_copyClip;
  elButtonCopy.innerHTML = "<span class='material-icons'>content_copy</span> " +
                           user_truncateStr( strClip );
  elButtonCopy.classList.add( "user_ButtonCopy" );

  let elButtonDel        = document.createElement( "button" );
  elButtonDel.type       = "button";
  elButtonDel.title      = "delete Clip"; // as tooltip
  elButtonDel.value      = strClip;
  elButtonDel.onclick    = user_deleteClip;
  elButtonDel.innerHTML  = "<span class='material-icons'>backspace</span>";
  elButtonDel.classList.add( "user_ButtonDelete" );

  let elButtonShi        = document.createElement( "button" );
  elButtonShi.type       = "button";
  elButtonShi.title      = "move to left"; // as tooltip
  elButtonShi.value      = strClip;
  elButtonShi.onclick    = user_shiftClip;
  elButtonShi.innerHTML  = "<span class='material-icons'>switch_left</span>";
  elButtonShi.classList.add( "user_ButtonShift" );

  elButtons.append( elButtonShi, elButtonCopy, elButtonDel );
  document.querySelector( "#" + selectorId ).append( " ", elButtons );
}
///▽ user_copyClip() ▽▽▽▽▽
function user_copyClip() {
  let str = this.value;  // this: "event.currentTarget"
  if( str !== "" ){
    navigator.clipboard.writeText( str );
    // https://developer.mozilla.org/docs/Web/API/Clipboard_API
    console.log( ">>> copied: '"+ user_truncateStr(str) + "'" );
  }
}
///▽ user_deleteClip() ▽
function user_deleteClip() {
  let selectorId = this.parentElement.parentElement.id;
  console.log( "=== Delete button in " + selectorId + " is clicked ===" );
  let strTarget  = this.value;

  let jsonArrClips = localStorage.getItem( selectorId );
  let arrClips     = [];
  if( jsonArrClips === null ){
    console.error( " = no data for the key: '" + selectorId + "'" );
  }else{
    arrClips       = JSON.parse( jsonArrClips );
  }

  let numIndex = arrClips.indexOf( strTarget );
  if( numIndex < 0 ){ console.error( " search Value error in DeleteClip " ); return; }
  console.log( "   [" + numIndex + "]: " + strTarget ); // no "user_truncateStr()" for operation mistakes
  arrClips.splice( numIndex, 1 );
  user_listClips( selectorId, arrClips );
  localStorage.setItem( selectorId, JSON.stringify( arrClips ) );
}
///▽ user_shiftClip() ▽
function user_shiftClip() {
   // span-selectorId(==StorageKey) > span-nowrap > button-clip
  let selectorId = this.parentElement.parentElement.id;
  console.log( "=== Shift button in " + selectorId + " is clicked ===" );
  let strTarget  = this.value;

  let jsonArrClips = localStorage.getItem( selectorId );
  let arrClips     = [];
  if( jsonArrClips === null ){
    console.error( " = no data for the key: '" + selectorId + "'" );
  }else{
    arrClips       = JSON.parse( jsonArrClips );
  }

  let numIndex = arrClips.indexOf( strTarget );
  if( numIndex < 0 ){
    console.error( " search Value error in ShiftClip " ); return;
  }else if( numIndex === 0 ){ // Leftmost
    console.log( "   [" + numIndex + "] to [" + (arrClips.length-1) + "]: " + user_truncateStr(strTarget) );
    arrClips.shift();
    arrClips.push( strTarget );
  }else if( numIndex > 0 ){
    console.log( "   [" + numIndex + "] to [" + (numIndex-1) + "]: " + user_truncateStr(strTarget) );
    arrClips.splice( numIndex, 1 );
    arrClips.splice( numIndex-1, 0, strTarget );
  }
  user_listClips( selectorId, arrClips );
  localStorage.setItem( selectorId, JSON.stringify( arrClips ) );
}
///▽ user_truncateStr(str) ▽
function user_truncateStr( str ) {
  const numLength = 30; // ◆◆◆EDIT◆◆◆ if you need
  return str.length <= numLength ?
          user_escapeHtml( str ) : ( user_escapeHtml( str ).substr(0, numLength) + "…" );
}
function user_escapeHtml(str) {
  if( typeof str !== 'string' ) { console.error( " non-string escaping " ); return str; }
  return str.replace(/&/g, '&')  // ←"&amp;";
            .replace(/'/g, ''') // ←"&#x27;";
            .replace(/`/g, '`') // ←"&#x60;";
            .replace(/"/g, '"') // ←"&quot;";
            .replace(/</g, '<')   // ←"&lt;";
            .replace(/>/g, '>');  // ←"&gt;";
}
</script>
<style>
button.user_ButtonCopy {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 0 0 0;
  margin        : 2px 0 2px 0;
}
button.user_ButtonCopy:hover {
  background    : #00bb00;
}
button.user_ButtonCopy:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_ButtonDelete {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 0 10px 10px 0;
  margin        : 2px 3px 2px 1px;
}
button.user_ButtonDelete:hover {
  background    : #00bb00;
}
button.user_ButtonDelete:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_ButtonShift {
  text-align    : center;
  font-size     : 1rem;
  line-height   : 1.2rem;
  color         : #ffffff;

  background    : #050505;
  padding       : 2px 7px;
  border        : 0;
  border-radius : 10px 0 0 10px;
  margin        : 2px 1px 2px 3px;
}
button.user_ButtonShift:hover {
  background    : #00bb00;
}
button.user_ButtonShift:active {
  box-shadow    : inset -2px -2px 3px rgba(255, 255, 255, .6),
                  inset 2px 2px 3px rgba(0, 0, 0, .6);
}
button.user_StorageClip {
  font-size     : 0.9rem;
  line-height   : 1rem;
  color         : #ffffff;

  background    : #009900;
  padding       : 3px 5px;
  border        : 2px solid #009900;
  border-radius : 5%;
  box-shadow    : 2px 2px 3px #666666;
  margin        : 2px 3px;

  cursor        : pointer;
  transition    : .3s;
}
button.user_StorageClip:hover {
  color         : #009900;
  background    : #ffffff;
  box-shadow    : none;
}
</style>

<button type="button" class="user_StorageClip"
 onclick="user_appendClip(
            'user_ReportTitle',
            qbpms.form.get( 'title' )
          )"><span class="material-icons">add_circle</span> Storage Clip</button>
<span id="user_ReportTitle"></span>

<p style="text-align:right"><button type="button" onclick="user_deleteAllClips()">
 <span class="material-icons">delete_forever</span> Clear All Clips
</button></p>
HTML/JavaScript (click to open)
<button type="button" class="user_StorageClip"
 onclick="user_appendClip(
            'user_RegionMaster',
            qbpms.form.get( 'q_RegionMaster' )
          )"><span class="material-icons">add_circle</span> Storage Clip</button>
<span id="user_RegionMaster"></span>

Switch between Edit and Read-Only

Switch between Edit and Read-Only

Input / Output

  • ← SELECT (SELECT_SINGLE) q_Answer
  • → STRING (STRING_TEXTFIELD) q_FreeText
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: