Latest “Task Form Decorations”: https://support.questetra.com/category/deco/
最新の”処理画面デコレーション”: https://support.questetra.com/ja/category/deco-ja/
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 コード)は、無料で自由に複製利用して頂けます。
- Generate Password with Click
- Overwrite Frequent Strings using Button Attribute
- Overwrite Frequent Strings using Button Content
- Overwrite Multiline String with Click
- Append Frequent Strings using Button Attribute
- Convert to Katakana
- Convert to Roman Letters
- Enter Number using NumPad Buttons
- Set Geolocation Data
- Check Geolocation Data
- Confirm Selected User
- Confirm Selected Value
- Check All Checkboxes
- Increment Date using +/- Button
- Set Date Range and Datetime
- Calculate Working Hours
- Copy to Clipboard
- Validate with Regular Expressions
- Show Process Detail
- Extract Process IDs
- Validate using Check-Digit
- Generate QR Code
- Preview HTML Code in New Window
- Extract URLs
- Export Text as UTF8B-File
- Check Role Members via REST API
- Check Form History via REST API
- Copy Input-History using REST API
- Clip to Browser Cookie
- Clip to Browser Storage
- Switch between Edit and Read-Only
The workflow app administrator sets the decorations in the properties of each data item. Knowledge of HTML/JavaScript is required. / ワークフローアプリの管理者は、各データ項目のプロパティ部分にて説明デコレーションを設定します。HTML/JavaScript の知識が必要です。

- M213: Guidance Descriptions on the Operating Screen (HTML/JavaScript)
- R2131: HTML Tags Available for Operation Form
- R2132: Questetra Form JavaScript API (en)
- MDN Web Docs: Document.querySelector() (en)
- MDN Web Docs: Document.querySelector() (ja)
- Older examples
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」です。このコードを "パスワード生成" ボタンを表示させたい項目の[説明]部に設定します。](https://i0.wp.com/support.questetra.com/wp-content/uploads/2022/09/deco-Generate-Password-with-Click-202209011.gif?resize=1200%2C338&ssl=1)
Input / Output
- → STRING (STRING_TEXTFIELD)
q_NewPassword
(update)
<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)
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>
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

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

Input / Output
- → STRING (STRING_TEXTAREA)
q_EmailBody
(update)
<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

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

Input / Output
- ⇆ STRING (STRING_TEXTFIELD):
q_SurnameKatakana
<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
<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>
Convert to Roman Letters

Input / Output
- ← STRING (STRING_TEXTFIELD):
q_SurnameKatakana
- → STRING (STRING_TEXTFIELD):
q_SurnameRomanized
- ← STRING (STRING_TEXTFIELD):
q_SurnameRomanized
- → STRING (STRING_TEXTFIELD):
q_EmailAddr
- ← STRING (STRING_TEXTFIELD):
<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
- ← STRING (STRING_TEXTFIELD):
<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

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

Input / Output
- → STRING (STRING_TEXTFIELD)
q_LatitudeLongitude
(ReadWrite) - →
span#user_GeolocationAPI_Status
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

Input / Output
- ← STRING (STRING_TEXTFIELD)
q_LatitudeLongitude
(ReadOnly) - → browsing context (new tab)
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

Input / Output
- ← QUSER
q_Reviewer
- →
span#user_footnote_Reviewer
<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

Input / Output
- ← SELECT (SELECT_SINGLE):
q_Currency
- →
span#user_notice01
<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

Input / Output
- → SELECT (SELECT_CHECKBOX):
q_Regions
<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

Input / Output
- ⇆ DATE (DATE_YMD):
q_DueDate
- →
span#user_footnote_DueDate
<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

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
<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>
<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>
<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

Input / Output
- ← DATETIME:
q_StartTime
- ← DATETIME:
q_EndTime
- ← NUMERIC (2 decimal places):
q_BreakTimes
- →
span#user_WorkingHours
(update)
- →
<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>
<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>
<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>
<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

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

Input / Output
- ← STRING (STRING_TEXTFIELD):
q_Email
- →
span#user_footnote_Email
<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)
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
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

Input / Output
- ⇆ STRING (STRING_TEXTFIELD):
q_CorporateNumber
<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
<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>
Generate QR Code

Input / Output
- ← STRING (STRING_TEXTAREA)
q_Url
- →
span#user_output_encode
<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

Input / Output
- ← STRING (STRING_TEXTAREA):
q_Str_EmailHtmlReport
- → window:
_Blank
<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
<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)
<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
- ←
<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

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
- ←
<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

Input / Output
- ← STRING (STRING_TEXTFIELD):
q_ContentsOfDuties
- →
span#user_Candidate_Form8
<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

Input / Output
- ← STRING (STRING_TEXTFIELD):
q_RegisteredTradeName
- →
span#user_CookieClips_RegisteredTradeName
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>

Input / Output
- ← STRING (STRING_TEXTFIELD):
q_FilterPhrase
- →
span#user_CookieClips_FilterPhrase
<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
<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>

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
- →
<button onclick='user_appendClip( "user_ApprovalTitle", "q_ApprovalTitle" );'
type='button' class='user_ButtonAppend'>+ CookieClip ⇒</button>
<span id="user_ApprovalTitle"></span>
<button onclick='user_appendClip( "user_ApprovalOverview", "q_ApprovalOverview" );'
type='button' class='user_ButtonAppend'>+ CookieClip ⇒</button>
<span id="user_ApprovalOverview"></span>
<button onclick='user_appendClip( "user_ApprovalDescription", "q_ApprovalDescription" );'
type='button' class='user_ButtonAppend'>+ CookieClip ⇒</button>
<span id="user_ApprovalDescription"></span>
<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

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)
- →
<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>
<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

Input / Output
- ← SELECT (SELECT_SINGLE)
q_Answer
- → STRING (STRING_TEXTFIELD)
q_FreeText
<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>
