【Garoon JavaScript API】ワークフロー申請画面からkintoneのレコードを検索してデータをセットする
警告
jQuery UIはv1.13をもってメンテナンスモードになりました。
この記事で紹介しているカスタマイズでは、ダイアログの表示にjQuery UIを利用しています。
SweetAlert
や
Micromodal
などのダイアログを表示するその他のライブラリを使うよう、コードの書き換えを検討してください。
概要
次の記事で紹介した、Garoonとkintoneの連携Tipsに続き、ワークフロー連携を紹介します。
「【Garoon JavaScript API】kintoneの事前費用申請アプリから内容を引き継いでGaroonの支払申請を作成する」
今回は、kintoneを開かずに、Garoonのワークフロー画面からkintoneのデータを検索して、そのデータをセットするという内容です。
前回よりも、もう一歩踏み込んだ連携を紹介します!
前提条件と注意事項
- このカスタマイズには、クラウド版Garoonまたはパッケージ版Garoon 4.6以降と、kintoneのスタンダードコース以上の契約が必要です。
- パッケージ版Garoonのユーザーは、記事内容に追加してコードを修正する必要があります。
- カスタマイズを実行するユーザーは、kintoneを利用している必要があります。
- ワークフローJavaScriptカスタマイズは、JavaScriptを適用してから申請されたワークフローが対象になります。
それ以前に申請されたワークフローには適用されません。
できること
kintoneのアプリで日々利用した交通費を管理してGaroonでワークフロー申請する場合、kintoneからGaroonへデータを連携します。
データの連携をkintoneカスタマイズで実現すると、Garoonで申請を作りたいときにkintoneの画面を開く必要があります。
この記事で紹介するカスタマイズでは、Garoonの画面からkintoneのレコードを検索して、目的のレコードのデータをもとに申請を作成できます。
完成イメージ
kintoneの「交通費申請」アプリに日々の交通費を登録し、月末にGaroonの「交通費申請」ワークフローで申請する場合の例です。
「申請者(検索用)」項目で名前を入力して、「kintone検索」をクリックすると、kintoneのレコードが検索され、Garoonへ設定されるという流れになります。
今回はkintoneのアプリにはJavaScript/CSSファイルの設定は不要です。
Garoonのワークフローの設定にのみ、JavaScript/CSSファイルを設定しカスタマイズしていきます。
kintoneアプリのフィールドコードは利用するため、まずはkintoneアプリの設定からはじめます。
kintoneアプリの設定手順
交通費申請アプリを作成する
最初はkintoneの交通費申請アプリを作成します。
完成すると、作成画面は次のようになります。
アプリストアの「交通費申請」アプリを修正していただくと、簡単に作れます。
アプリの具体的な作成方法は、以下のヘルプを参照してください。
アプリをはじめから作成する
フィールドは以下のとおり設定してください。
フィールドコードは、Garoon側に設定するJavaScriptコード内でそれぞれのフィールドを指定するための一意の文字列となります。
間違えないように設定してください。
フィールド名 | フィールドコード | フィールドタイプ | 備考 |
---|---|---|---|
申請者 | Author | 文字列(一行) | 元の申請者フィールドを削除して文字列(一行)フィールドを追加します。 |
申請月 | ApplicationMonth | 文字列(一行) | 文字列(一行)フィールドを追加します。 |
社員番号 | EmployeeNumber | 数値 | |
所属部署 | Department | ドロップダウン | |
承認者 | Authorizer | ユーザー選択 | |
タイトル | Title | 文字列(一行) | |
日付(テーブル) | Date |
日付 | 「日付」から「金額」まではテーブルとして設定します。 |
訪問先(テーブル) | Destination | 文字列(一行) | |
交通手段(テーブル) | Transportation | ドロップダウン | ドロップダウンの項目を削除し、次の値を設定します。
|
適用(テーブル) | Application | ラジオボタン | ラジオボタンフィールドを追加し、項目には次の値を設定します。
|
金額(テーブル) | Amount | 数値 | |
(テーブルの設定) | LineItem | テーブル | テーブルのフィールドコードです。 |
合計金額 | Total | 計算 | |
備考 | Remarks | 文字列(複数行) |
カスタマイズを実行するユーザーに対し、アプリのレコード閲覧権限を設定してください。
これでkintoneアプリの設定は完了です!
Garoon側で設定するJavaScriptファイルの中にkintoneのアプリIDを入力しますので、アプリIDは覚えておいてください。
例)https://xxxxx.cybozu.com/k/xxx/ ←アプリIDはこの太字の部分に書いてある数字です。
Garoonワークフローの設定手順
ワークフローの項目の内容は、会社さんによって異なります。
ここでは、完成イメージで利用した支払申請の申請フォームにJavaScript/CSSカスタマイズを設定する流れを説明します。
①Garoonワークフローの申請フォームを作成する
まずはkintoneでアプリを準備したのと同じく、Garoonで以下の項目を配置して、支払申請ワークフローの申請フォームを作成していきます。
申請フォームの作成方法については、Garoonヘルプ - 申請フォームの作成の流れ
クラウド版
・
パッケージ版
を参照してください。
フォーム作成は少し手間がかかるので、今回はそのまま読み込んで使えるサンプルフォームもご用意しています。(後述)
それぞれの項目は以下のとおり設定してください。
ここでも項目コードは、JavaScriptコード内でそれぞれの項目を指定するための一意の文字列になります。
項目数が多いですが、こちらも間違えないように設定してください。
項目名 | 項目タイプ | 項目コード | 備考 |
---|---|---|---|
標題 | 文字列(1行)(標準項目) | Title | 必須項目に設定 |
申請者(検索用) | 文字列(1行) | ApplicantForSearch | |
(JavaScriptカスタマイズ用項目) | JavaScriptカスタマイズ用 | AddButton | |
所属組織 | メニュー | ||
(空白行) | (空白行) | - | 「空白行を追加する」で設定 |
1. | 日付 | SUB1_ITEM1_1 | |
行き先 | 文字列(1行) | SUB1_ITEM1_2 | |
詳細 | メニュー | SUB1_ITEM1_3 | |
適用 | ラジオボタン | SUB1_ITEM1_4 | |
金額 | 数値 | SUB1_ITEM1_5 | |
(空白行) | (空白行) | - | 「空白行を追加する」で設定 |
2. | 日付 | SUB1_ITEM2_1 | |
行き先 | 文字列(1行) | SUB1_ITEM2_2 | |
詳細 | メニュー | SUB1_ITEM2_3 | |
適用 | ラジオボタン | SUB1_ITEM2_4 | |
金額 | 数値 | SUB1_ITEM2_5 | |
(空白行) | (空白行) | - | 「空白行を追加する」で設定 |
3. | 日付 | SUB1_ITEM3_1 | |
行き先 | 文字列(1行) | SUB1_ITEM3_2 | |
詳細 | メニュー | SUB1_ITEM3_3 | |
適用 | ラジオボタン | SUB1_ITEM3_4 | |
金額 | 数値 | SUB1_ITEM3_5 | |
(空白行) | (空白行) | - | 「空白行を追加する」で設定 |
4. | 日付 | SUB1_ITEM4_1 | |
行き先 | 文字列(1行) | SUB1_ITEM4_2 | |
詳細 | メニュー | SUB1_ITEM4_3 | |
適用 | ラジオボタン | SUB1_ITEM4_4 | |
金額 | 数値 | SUB1_ITEM4_5 | |
(空白行) | (空白行) | - | 「空白行を追加する」で設定 |
5. | 日付 | SUB1_ITEM5_1 | |
行き先 | 文字列(1行) | SUB1_ITEM5_2 | |
詳細 | メニュー | SUB1_ITEM5_3 | |
適用 | ラジオボタン | SUB1_ITEM5_4 | |
金額 | 数値 | SUB1_ITEM5_5 | |
(空白行) | (空白行) | - | 「空白行を追加する」で設定 |
合計金額 | 自動計算 | ||
(空白行) | (空白行) | - | 「空白行を追加する」で設定 |
備考 | 文字列(複数行) | Remarks |
上記のとおり設定が完了したら、土台となる申請フォームの作成は完了です。
サンプルフォームのダウンロードについて
申請フォームを作成するのに少し時間がかかるので、まずは動きを見てみたいという方向けに、そのまま環境へ読み込んで使っていただけるサンプルの申請フォームをXML形式でご用意しました。
完成イメージにある支払申請の申請フォームです。
以下のリンクを右クリックして、コンテキストメニューから[リンク先を別名で保存]をクリックしてダウンロードしてください。
このXMLファイルを、「申請フォーム一覧」から読み込んでいただくと、「【サンプル】交通費申請」という申請フォームが追加されます。
項目コードも設定済みの状態です。
サンプルフォームを追加する方法については、
XMLファイルから読み込む
を参照してください。
経路情報は各環境で異なり、存在しない経路を読み込むことはできないので、上記のXMLでは経路情報を省いています。
申請フォームを有効化するには、経路情報の設定が必要になりますので、そちらも忘れず設定してください。
②JavaScript/CSSファイルを適用する
申請フォームの作成が完了したので、あと一歩です。
ここから作成した申請フォームにJavaScriptファイルを適用していきます。
設定するJavaScript/CSSファイルが少し多いですが、頑張ってください!
適用ファイルの準備
下記3つのサンプルコードをコピーしてPCに保存します。
-
grkin_common.js
次のサンプルコードをエディタにコピーして、ファイル名を「grkin_common.js」、文字コードを「UTF-8」で保存します。
ファイル名は任意ですが、ファイルの拡張子は「js」にしてください。
16行目には、kintoneアプリのIDを入力してください。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
/** * Garoon JavaScript APIを使ったサンプルプログラム * 「grkin_common.js」ファイル * Copyright (c) 2017 Cybozu * * Licensed under the MIT License * https://opensource.org/license/mit/ */ (function() { 'use strict'; window.garoonWFkintone = { appId: { // 1はkintoneのアプリIDに差し替える kintone: 1 }, fieldCode: { search: { // Garoonワークフローの「申請者(検索用)」項目の項目コード garoon: 'ApplicantForSearch', // kintoneアプリの「申請者」項目のフィールドコード kintone: 'Author' }, selectItems: { // Garoonワークフロー申請画面で、申請者(検索用)項目に表示している [kintone検索] // をクリックした時に表示される、「kintone検索結果画面」に表示する項目 // 例)申請者、タイトル、申請月 kintone: ['Author', 'Title', 'ApplicationMonth'] } }, table: { // kintoneアプリのサブテーブルを連携対象とする場合に記入する場所 kintone: { // テーブルのフィールドコードと、テーブルに含まれている項目のフィールドコードを記入する LineItem: ['Date', 'Destination', 'Transportation', 'Application', 'Amount'] }, garoon: { // Garoonワークフローの項目コード:[最大の行数, 最大の列数] // kintoneアプリのテーブルを連携対象としない場合は下記の1行を削除 SUB1: [5, 5] } }, relation: { // 関連付けの設定 fields: { // 左側がガルーンの項目コード:右側がkintoneのフィールドコード Title: 'Title', Remarks: 'Remarks' }, subTable: { // 左側がkintoneのフィールドコード:右側がガルーンの項目コード LineItem: 'SUB1' } } }; })();
-
grkin.js
次のサンプルコードも同様にエディタにコピーして保存します。ファイル名を「grkin.js」、文字コードを「UTF-8」で保存してください。
ファイル名は任意ですが、ファイルの拡張子は「js」にしてください。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
/** * Garoon JavaScript APIを使ったサンプルプログラム * 「grkin.js」ファイル * Copyright (c) 2017 Cybozu * * Licensed under the MIT License * https://opensource.org/license/mit/ */ (function($) { 'use strict'; // 「grkin_common.js」の値を取得 const KINTONE_APP_ID = window.garoonWFkintone.appId.kintone; const GR_SEARCH_FIELD = window.garoonWFkintone.fieldCode.search.garoon; const KIN_SEARCH_FIELD = window.garoonWFkintone.fieldCode.search.kintone; const SELECT_ITEM = window.garoonWFkintone.fieldCode.selectItems.kintone; const GAROON_TABLE_INFO = window.garoonWFkintone.table.garoon; const KINTONE_TABLE_INFO = window.garoonWFkintone.table.kintone; const FIELD_RELATION = window.garoonWFkintone.relation.fields; const SUBTABLE_RELATION = window.garoonWFkintone.relation.subTable; // 空の配列を作成 const FIELD_TYPE_INFO = []; const FIELD_LABEL_INFO = []; // Garoonのワークフローで表示している独自のボタンの文言 const GR_BUTTON_SEARCH_ON_KINTONE = 'kintone検索'; const DIALOG_BUTTON_CANCEL = 'キャンセル'; const DIALOG_BUTTON_OK = 'OK'; const DIALOG_BUTTON_SELECT = '選択'; // [kintone検索結果]ダイアログで利用している文言 const DIALOG_MESSAGE_NODATA = 'データがありません。選択された項目はクリアされます。'; const DIALOG_MESSAGE_SUB_TOP = 'フォームに取り込む値を選択してください。'; const DIALOG_MESSAGE_TOP = 'kintone検索結果'; const DIALOG_MESSAGE_COUNT = '件'; // エラーメッセージ const ERROR_MESSAGE_SEARCH_ON_KINTONE = 'kintoneデータの取得に失敗しました。'; const ERROR_MESSAGE_GET_FORM_IFNO = 'form情報の取得に失敗しました。'; const ERROR_MESSAGE_SETTING_NOT_MATCHED = 'kintoneのサブテーブルとGaroon側の設定が合っていません。'; const ERROR_MESSAGE_NO_BUTTON = 'kintone検索用のボタンが無いか\nまたはフィールドコードが誤っています。'; const ERROR_MESSAGE_NO_SEARCH_FIELD = 'kintone検索文字列用のフィールドが無いか\nまたはフィールドコードが誤っています。'; // 空のオブジェクトを作成 let RADIO_VALUES = {}; /** * @param {string} htmlStr * @return {string} htmlStr */ function escapeHtml(htmlStr) { if (htmlStr === null) { return ''; } try { return htmlStr.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') .replace(/"/g, '"').replace(/'/g, ''').replace(',', '%,'); } catch (e) { return htmlStr; } } /** * @param {string} str * @return {string} str */ function escapeCanma(str) { if (str === null) { return ''; } try { return str.replace('%', '%c').replace(',', '%d'); } catch (e) { return str; } } /** * @param {string} grField * @param {string} value */ function setRadioItem(grField, value) { $('.js_customization_input_item_' + grField).find('input').val([value]); } const Spin = { spinner: new Spinner({ lines: 13, length: 28, width: 14, radius: 42, scale: 1, corners: 1, color: '#FFF', opacity: 0.25, rotate: 0, direction: 1, speed: 1, trail: 60, fps: 20, zIndex: 2e9, className: 'spinner', top: '50%', left: '50%', shadow: false, hwaccel: false, position: 'fixed' }), showSpinner: function() { if ($('.kintone-spinner').length === 0) { const spinBgDiv = $('<div id ="kintone-spin-bg" class="kintone-spinner"></div>'); $(document.body).append(spinBgDiv); } $('.kintone-spinner').show(); this.spinner.spin($('html')[0]); }, hideSpinner: function() { $('.kintone-spinner').hide(); this.spinner.stop(); } }; const SettingGaroonData = { /** * Set data in Garoon * @param {object} data * @param {object} request */ setGaroonData: function(data, request) { let req = request; RADIO_VALUES = {}; for (const grField in FIELD_RELATION) { if (!Object.prototype.hasOwnProperty.call(FIELD_RELATION, grField)) { continue; } const kinField = FIELD_RELATION[grField]; const fieldType = FIELD_TYPE_INFO[kinField]; const kinData = data[kinField].value; req = SettingGaroonData.setItem(grField, fieldType, kinData, req); } if (!$.isEmptyObject(GAROON_TABLE_INFO) && !$.isEmptyObject(KINTONE_TABLE_INFO)) { req = SettingGaroonData.setTableData(data, req); } garoon.workflow.request.set(req); for (const key in RADIO_VALUES) { if (!Object.prototype.hasOwnProperty.call(RADIO_VALUES, key)) { setRadioItem(key, RADIO_VALUES[key]); } } }, /** * Set data to Garoon from the dialog * @param {object} data * @param {object} request */ setGaroonDataForDialog: function(data, request) { let req = request; RADIO_VALUES = {}; for (const grField in FIELD_RELATION) { if (!Object.prototype.hasOwnProperty.call(FIELD_RELATION, grField)) { continue; } const kinField = FIELD_RELATION[grField]; const kinData = data[kinField]; const fieldType = FIELD_TYPE_INFO[kinField]; if (!Object.prototype.hasOwnProperty.call(data, kinField)) { continue; } req = SettingGaroonData.setItem(grField, fieldType, kinData, req); } if (!$.isEmptyObject(GAROON_TABLE_INFO) && !$.isEmptyObject(KINTONE_TABLE_INFO)) { req = SettingGaroonData.setTableDataForDialog(data, req); } garoon.workflow.request.set(req); for (const key in RADIO_VALUES) { if (Object.prototype.hasOwnProperty.call(RADIO_VALUES, key)) { setRadioItem(key, RADIO_VALUES[key]); } } }, /** * Set sub table data in Garoon * @param {object} data * @param {object} request * @return {object} req */ setTableData: function(data, request) { let req = request; for (const tableCode in KINTONE_TABLE_INFO) { if (!Object.prototype.hasOwnProperty.call(KINTONE_TABLE_INFO, tableCode)) { continue; } const kinTableData = data[tableCode].value; const kinFieldTypes = KINTONE_TABLE_INFO[tableCode]; const grTableCode = SUBTABLE_RELATION[tableCode]; const maxRow = GAROON_TABLE_INFO[grTableCode][0]; const maxColumn = GAROON_TABLE_INFO[grTableCode][1]; const rowIdx = 1; if (maxRow < kinTableData.length || maxColumn < kinFieldTypes.length) { swal('Error!', ERROR_MESSAGE_SETTING_NOT_MATCHED, 'error'); return req; } req = SettingGaroonData.clearSubtableData(grTableCode, kinFieldTypes, maxRow, maxColumn, req); req = SettingGaroonData.loopSubtableData(kinTableData, kinFieldTypes, grTableCode, rowIdx, req); } return req; }, /** * Loop processing of sub table data * @param {object} kinTableData * @param {string} kinFieldTypes * @param {string} grTableCode * @param {number} rowNum * @param {object} request * @return {object} req */ loopSubtableData: function(kinTableData, kinFieldTypes, grTableCode, rowNum, request) { let req = request; let rowIdx = rowNum; for (let i = 0; i < kinTableData.length; i++) { const tableValue = kinTableData[i].value; let columIdx = 1; for (let j = 0; j < kinFieldTypes.length; j++) { const fieldValue = tableValue[kinFieldTypes[j]].value; const fieldType = tableValue[kinFieldTypes[j]].type; const grFieldCode = `${grTableCode}_ITEM${rowIdx}_${columIdx}`; req = SettingGaroonData.setItem(grFieldCode, fieldType, fieldValue, req); if (fieldType !== 'RADIO_BUTTON') { req = SettingGaroonData.setItem(grFieldCode, fieldType, fieldValue, req); } else { RADIO_VALUES[grFieldCode] = fieldValue; } columIdx++; } rowIdx++; } return req; }, /** * Set sub table data in Garoon from the dialog * @param {object} data * @param {object} request * @return {object} req */ setTableDataForDialog: function(data, request) { let req = request; for (const key in KINTONE_TABLE_INFO) { if (!Object.prototype.hasOwnProperty.call(KINTONE_TABLE_INFO, key)) { continue; } const kinTableData = data[key]; const fieldTypes = KINTONE_TABLE_INFO[key]; const grField = SUBTABLE_RELATION[key]; const maxRow = GAROON_TABLE_INFO[grField][0]; const maxColumn = GAROON_TABLE_INFO[grField][1]; const rowIdx = 1; if (maxRow < kinTableData.length || maxColumn < fieldTypes.length) { swal('Error!', ERROR_MESSAGE_SETTING_NOT_MATCHED, 'error'); return req; } req = SettingGaroonData.clearSubtableData(grField, fieldTypes, maxRow, maxColumn, req); req = SettingGaroonData.loopSubtableDataForDaialog(kinTableData, fieldTypes, grField, rowIdx, req); } return req; }, /** * Loop processing of sub table data from the dialog * @param {object} kinTableData * @param {string} fieldTypes * @param {string} grField * @param {number} rowNum * @param {object} request * @return {object} req */ loopSubtableDataForDaialog: function(kinTableData, fieldTypes, grField, rowNum, request) { let req = request; let rowIdx = rowNum; for (let i = 0; i < kinTableData.length; i++) { if (kinTableData[i] === undefined) { continue; } const fieldData = kinTableData[i].split(','); let columIdx = 1; for (let j = 0; j < fieldData.length; j++) { const fieldType = FIELD_TYPE_INFO[fieldTypes[j]]; const kinData = (fieldData[j]).replace('%c', '%').replace('%d', ','); const grCode = `${grField}_ITEM${rowIdx}_${columIdx}`; if (fieldType !== 'RADIO_BUTTON') { req = SettingGaroonData.setItem(grCode, fieldType, kinData, req); } else { RADIO_VALUES[grCode] = kinData; } columIdx++; } rowIdx++; } return req; }, /** * Clear sub table data set on Garoon * @param {string} grField * @param {string} fieldTypes * @param {number} maxRow * @param {number} maxColumn * @param {object} req * @return {object} req */ clearSubtableData: function(grField, fieldTypes, maxRow, maxColumn, req) { for (let j = 1; j <= maxRow; j++) { for (let k = 1; k <= maxColumn; k++) { const fieldType = FIELD_TYPE_INFO[fieldTypes[k - 1]]; const grCode = `${grField}_ITEM${j}_${k}`; switch (fieldType) { case 'DATE': req.items[grCode].value = null; break; case 'DATETIME': req.items[grCode].value = null; break; default: req.items[grCode].value = ''; break; } } } return req; }, /** * Set the value * @param {string} grField * @param {string} fieldTypes * @param {object} kinData * @param {object} req * @return {object} req */ setItem: function(grField, fieldType, kinData, req) { switch (fieldType) { case 'DATE': { if (kinData === 'null') { break; } req.items[grField].value = kinData; break; } case 'DATETIME': { if (kinData === '') { break; } const d = luxon.DateTime.fromISO(kinData).toFormat('yyyy-MM-dd'); const t = luxon.DateTime.fromISO(kinData).toFormat('HH:mm'); req.items[grField].value = { date: d, time: t }; break; } case 'DROP_DOWN': { if (kinData === 'null') { break; } req.items[grField].value = kinData; break; } case 'RADIO_BUTTON': { RADIO_VALUES[grField] = kinData; break; } default: { req.items[grField].value = kinData; break; } } return req; } }; /** * If there is no search data, clear the setting value for sub data * @param {object} request * @return {object} request */ function clearSubFieldData(request) { let req = request; for (const key in KINTONE_TABLE_INFO) { if (!Object.prototype.hasOwnProperty.call(KINTONE_TABLE_INFO, key)) { continue; } const fieldTypes = KINTONE_TABLE_INFO[key]; const grField = SUBTABLE_RELATION[key]; const maxRow = GAROON_TABLE_INFO[grField][0]; const maxColumn = GAROON_TABLE_INFO[grField][1]; req = SettingGaroonData.clearSubtableData(grField, fieldTypes, maxRow, maxColumn, req); } return req; } /** * If there is no search data, clear the setting value * @param {object} request * @return {object} request */ function clearFiledData(request) { let req = request; req.items[GR_SEARCH_FIELD].value = ''; for (const grField in FIELD_RELATION) { if (!Object.prototype.hasOwnProperty.call(FIELD_RELATION, grField)) { continue; } const kinField = FIELD_RELATION[grField]; const fieldType = FIELD_TYPE_INFO[kinField]; switch (fieldType) { case 'DATE': req.items[grField].value = null; break; case 'DATETIME': req.items[grField].value = null; break; default: req.items[grField].value = ''; break; } if (!$.isEmptyObject(GAROON_TABLE_INFO) && !$.isEmptyObject(KINTONE_TABLE_INFO)) { req = clearSubFieldData(request); } } return req; } /** * Dialog function */ const SelectDialog = { /** * get values from the dialog * @param {object} el * @return {object} dataInfo */ getDialogData: function(el) { const dataInfo = []; for (const key in FIELD_RELATION) { if (!Object.prototype.hasOwnProperty.call(FIELD_RELATION, key)) { continue; } const kinField = FIELD_RELATION[key]; dataInfo[kinField] = el.find('#' + kinField).val(); } if (!$.isEmptyObject(GAROON_TABLE_INFO) && !$.isEmptyObject(KINTONE_TABLE_INFO)) { for (const tableCode in KINTONE_TABLE_INFO) { if (!Object.prototype.hasOwnProperty.call(KINTONE_TABLE_INFO, tableCode)) { continue; } const valueArr = []; const num = $(el).find('input[id^=' + tableCode + '_]').length; for (let i = 0; i < num; i++) { valueArr.push(el.find('#' + tableCode + '_' + i).val()); } dataInfo[tableCode] = valueArr; } } return dataInfo; }, /** * Display data in dialog * @param {string} dataHtml * @param {object} request */ showDialog: function(dataHtml, request, dataFlag) { let req = request; let buttonName; const $dateDialog = $('<div>'); $dateDialog.attr('id', 'kintone-dialog'); $dateDialog.html(dataHtml); if (dataFlag === 1) { buttonName = DIALOG_BUTTON_CANCEL; } else { buttonName = DIALOG_BUTTON_OK; } const button = {}; button[buttonName] = function() { $(this).dialog('close'); $(this).remove(); }; $dateDialog.dialog({ title: DIALOG_MESSAGE_TOP, autoOpen: false, width: 'auto', maxHeight: 700, show: 400, hide: 400, modal: true, buttons: button, close: function(event, ui) { if (buttonName === DIALOG_BUTTON_OK) { req = clearFiledData(request); garoon.workflow.request.set(req); } } }); $('#kintone-dialog').dialog('open'); $('.select_btn').click(function() { const params = SelectDialog.getDialogData($(this).parents('.kintone-select-tr')); SettingGaroonData.setGaroonDataForDialog(params, request); $('#kintone-dialog').dialog('close'); $('#kintone-dialog').remove(); }); } }; const CreateHtml = { /** * Create a selection list for dialog display * @param {object} kinRec * @return {string} result */ createSelectList: function(kinRec) { let datalist = ''; let count = 0; for (let i = 0; i < kinRec.records.length; i++) { const kintoneRecord = kinRec.records[i]; datalist += '<tr id="selectlist_' + i + '" class="kintone-select-tr">' + '<td class="select-cell-kintone">' + '<span><button class="button-simple-custom select_btn" type="button">' + DIALOG_BUTTON_SELECT + '</button></span></td>' + CreateHtml.createSelectData(kintoneRecord) + CreateHtml.createInputData(kintoneRecord) + '</tr>'; count++; } const result = '<span class="selectexternalItemWindow_text">' + DIALOG_MESSAGE_SUB_TOP + '</span>' + '<table class="listTable-kintone select-table-kintone">' + '<thead class="select-thead-gaia">' + '<tr>' + '<th><div><span class="recordlist-header-label-kintone">' + count + DIALOG_MESSAGE_COUNT + '</span></div></th>' + CreateHtml.createHeading() + '</tr>' + '</thead><tbody>' + datalist + '</tbody>' + '</table>'; return result; }, /** * Create header section * @return {string} headingData */ createHeading: function() { let headingData = ''; for (let i = 0; i < SELECT_ITEM.length; i++) { const filedCode = SELECT_ITEM[i]; const filedLabel = FIELD_LABEL_INFO[filedCode]; headingData += '<th><div><span class="recordlist-header-label-kintone">' + filedLabel + '</span></div></th>'; } return headingData; }, /** * Create select section * @param {object} record * @return {string} selectData */ createSelectData: function(record) { let selectData = ''; for (let i = 0; i < SELECT_ITEM.length; i++) { const filedCode = SELECT_ITEM[i]; selectData += '<td><div class="line-cell-kintone"><span>' + escapeHtml(record[filedCode].value) + '</span></div>'; } return selectData; }, /** * Create Input data section * @param {object} record * @return {string} inputData */ createInputData: function(record) { let inputData = ''; for (const key in FIELD_RELATION) { if (!Object.prototype.hasOwnProperty.call(FIELD_RELATION, key)) { continue; } const kinField = FIELD_RELATION[key]; inputData += '<input id="' + kinField + '" value="' + escapeHtml(record[kinField].value) + '" type="hidden">'; } if (!$.isEmptyObject(GAROON_TABLE_INFO) && !$.isEmptyObject(KINTONE_TABLE_INFO)) { for (const tableCode in KINTONE_TABLE_INFO) { if (!Object.prototype.hasOwnProperty.call(KINTONE_TABLE_INFO, tableCode)) { continue; } const kinTableData = record[tableCode]; const kinTableField = KINTONE_TABLE_INFO[tableCode]; for (let j = 0; j < kinTableData.value.length; j++) { const rowData = kinTableData.value[j]; const valueArr = []; for (let k = 0; k < kinTableField.length; k++) { valueArr.push(escapeCanma(rowData.value[kinTableField[k]].value)); } inputData += '<input id="' + tableCode + '_' + j + '" value="' + valueArr + '" type="hidden">'; } } } return inputData; }, /** * create NoData message * @return {string} nodataMsg */ createNoData: function() { const nodataMsg = '<div id="kintone_lookup_validator_error" class="input-error-custom">' + '<span>' + DIALOG_MESSAGE_NODATA + '</span></div>'; return nodataMsg; } }; const createFormInfo = { /** * Get form information on kintone * @param {object} record */ getFormInfo: function(record) { $.ajax({ type: 'GET', url: '/k/v1/app/form/fields.json', data: {app: KINTONE_APP_ID}, cache: false, dataType: 'json' }).done((resp) => { for (const i in resp.properties) { if (Object.prototype.hasOwnProperty.call(resp.properties, i)) { FIELD_TYPE_INFO[[resp.properties[i].code]] = resp.properties[i].type; FIELD_LABEL_INFO[[resp.properties[i].code]] = resp.properties[i].label; if (resp.properties[i].type === 'SUBTABLE') { const fields = resp.properties[i].fields; for (const j in fields) { // eslint-disable-next-line max-depth if (!Object.prototype.hasOwnProperty.call(fields, j)) { continue; } FIELD_TYPE_INFO[[fields[j].code]] = fields[j].type; FIELD_LABEL_INFO[[fields[j].code]] = fields[j].label; } } } } }).fail((error) => { Spin.hideSpinner(); swal('Error!', ERROR_MESSAGE_GET_FORM_IFNO, 'error'); }); } }; /** * Grkin cooperation execution */ function execGrkin() { const request = garoon.workflow.request.get(); if (request.items[GR_SEARCH_FIELD] === undefined) { swal('Error!', ERROR_MESSAGE_NO_SEARCH_FIELD, 'error'); return; } Spin.showSpinner(); const searchWord = request.items[GR_SEARCH_FIELD].value; let query = KIN_SEARCH_FIELD + ' like "' + searchWord + '"'; if (!searchWord) { query = ''; } $.ajax({ type: 'GET', url: '/k/v1/records.json', data: {app: KINTONE_APP_ID, query: query}, cache: false, dataType: 'json' }).done((resp) => { Spin.hideSpinner(); let dataHtml; if (resp.records.length === 0) { dataHtml = CreateHtml.createNoData(); SelectDialog.showDialog(dataHtml, request, 0); } else if (resp.records.length === 1) { SettingGaroonData.setGaroonData(resp.records[0], request); } else { dataHtml = CreateHtml.createSelectList(resp); SelectDialog.showDialog(dataHtml, request, 1); } }).fail((error) => { Spin.hideSpinner(); swal('Error!', ERROR_MESSAGE_SEARCH_ON_KINTONE, 'error'); }); } /** * Disable editable fields for sub table * @param {string} key */ function disableSubFiled(key) { const maxRow = GAROON_TABLE_INFO[key][0]; const maxColumn = GAROON_TABLE_INFO[key][1]; for (let j = 1; j <= maxRow; j++) { for (let k = 1; k <= maxColumn; k++) { if ($('.js_customization_input_item_' + key + '_ITEM' + j + '_' + k)[0].nodeName !== 'TD') { $('.js_customization_input_item_' + key + '_ITEM' + j + '_' + k) .css('background-color', 'rgb(235, 235, 228)') .on('keyup keydown keypress select click mousedown', (e) => { e.preventDefault(); }); } else { $('.js_customization_input_item_' + key + '_ITEM' + j + '_' + k).children('select') .css('background-color', 'rgb(235, 235, 228)') .on('keyup keydown keypress select click mousedown', (e) => { e.preventDefault(); }); $('.js_customization_input_item_' + key + '_ITEM' + j + '_' + k).children('a') .on('keyup keydown keypress select click mousedown', function(e) { $(this).css('pointer-events', 'none'); }); $('.js_customization_input_item_' + key + '_ITEM' + j + '_' + k).children('input') .css('background-color', 'rgb(235, 235, 228)') .on('keyup keydown keypress select click mousedown', (e) => { e.preventDefault(); }); } } } } /** * Disable editable fields */ function disableFiled() { for (const grField in FIELD_RELATION) { if (!Object.prototype.hasOwnProperty.call(FIELD_RELATION, grField)) { continue; } if ($('.js_customization_input_item_' + grField)[0].nodeName.toLowerCase() !== 'td') { $('.js_customization_input_item_' + grField) .css('background-color', 'rgb(235, 235, 228)') .on('keyup keydown keypress select click mousedown', (e) => { e.preventDefault(); }); } else { $('.js_customization_input_item_' + grField).children('select') .css('background-color', 'rgb(235, 235, 228)') .on('keyup keydown keypress select click mousedown', (e) => { e.preventDefault(); }); $('.js_customization_input_item_' + grField).children('a') .on('keyup keydown keypress select click mousedown', function(e) { $(this).css('pointer-events', 'none'); }); $('.js_customization_input_item_' + grField).children('input') .css('background-color', 'rgb(235, 235, 228)') .on('keyup keydown keypress select click mousedown', (e) => { e.preventDefault(); }); } if (!$.isEmptyObject(GAROON_TABLE_INFO) && !$.isEmptyObject(KINTONE_TABLE_INFO)) { for (const key in GAROON_TABLE_INFO) { if (!Object.prototype.hasOwnProperty.call(GAROON_TABLE_INFO, key)) { continue; } disableSubFiled(key); } } } } /** * Application form additional event * @param {object} event * @return {object} event */ garoon.events.on('workflow.request.create.show', (event) => { disableFiled(); const space = garoon.workflow.request.getSpaceElement('AddButton'); if (space === null) { swal('Error!', ERROR_MESSAGE_NO_BUTTON, 'error'); return; } createFormInfo.getFormInfo(); const addButtonEl = document.createElement('input'); addButtonEl.type = 'button'; addButtonEl.value = GR_BUTTON_SEARCH_ON_KINTONE; addButtonEl.id = 'addBtn'; space.appendChild(addButtonEl); $('#addBtn').click(() => { $('#kintone-dialog').remove(); execGrkin(); }); return event; }); })(jQuery.noConflict(true));
-
grkin.css
次のサンプルコードも同様にエディタにコピーして保存します。ファイル名を「grkin.css」、文字コードを「UTF-8」で保存してください。
ファイル名は任意ですが、ファイルの拡張子は「css」にしてください。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
/** * Garoon JavaScript APIを使ったサンプルプログラム * * 「grkin.css」ファイル * * Copyright (c) 2017 Cybozu * * Licensed under the MIT License */ .listTable-kintone.select-table-kintone { width: auto; table-layout: fixed; background-color: #fff; border-spacing: 0; font-size: 14px; margin-top: 13px; } .listTable-kintone.select-table-kintone th { width: auto; color: #333333; padding: 2px 10px; background-color: #9DBEC6; border: 1px solid #cccccc; line-height: 120%; text-align: center; } .listTable-kintone.select-table-kintone th:first-child { width: 80px; border-radius: 5px 0 0 0; } .listTable-kintone.select-table-kintone th:last-child { border-radius:0 5px 0 0; /*border-right:1px solid #258;*/ border: 1px solid #cccccc; box-shadow: 2px 2px 1px rgba(0,0,0,0.1),0px 1px 1px rgba(255,255,255,0.3) inset; } .listTable-kintone.select-table-kintone tr td { text-align: center; border: 1px solid #cccccc; } .listTable-kintone.select-table-kintone tr td:last-child { /*border-right: 1px solid #84b2e0;*/ border: 1px solid #cccccc; box-shadow: 2px 2px 1px rgba(0,0,0,0.1); } .listTable-kintone.select-table-kintone tr:last-child td { box-shadow: 2px 2px 1px rgba(0,0,0,0.1); } .listTable-kintone.select-table-kintone tr:last-child td:first-child { border-radius: 0 0 0 5px; } .listTable-kintone.select-table-kintone tr:last-child td:last-child { border-radius: 0 0 5px 0; } .listTable-kintone.select-table-kintone tbody tr:nth-child(2n) { /*background-color: #f7f9fa*/ background-color: #E6F1F2; } .listTable-kintone.select-table-kintone td>div,.listTable-kintone.select-table-kintone th>div { /*padding: 16px 16px 8px*/ padding: 5px 5px 1px } .listTable-kintone.select-table-kintone tbody tr:focus { background-color: #f5f5f5 } .button-simple-custom { margin: 5px } #kintone-spin-bg { position: fixed; top: 0px; left: 0px; z-index: 500; width: 100%; height: 1000%; background-color: #000; opacity: 0.5; filter: alpha(opacity=50); -ms-filter: alpha(opacity=50); }
JavaScript/CSSファイルとして使用するファイルのおよびリンクの追加
- 「申請フォーム情報」部分の右端にある「JavaScript / CSSによるカスタマイズ」をクリックします。
ワークフローのカスタマイズが許可されていない場合、申請フォームの詳細画面に「JavaScript / CSSによるカスタマイズ」リンクが表示されません。
Garoonヘルプ - ワークフローのカスタマイズを許可する クラウド版 ・ パッケージ版 を参照してください。
- [カスタマイズ]項目に「適用する」を選択してください。
- [JavaScriptカスタマイズ]項目に下記を設定します。
- Cybozu CDNの次のライブラリを使用しますので、次のリンクを追加してください。
- jQuery
https://js.cybozu.com/jquery/3.6.4/jquery.min.js - JQuery UI
https://js.cybozu.com/jqueryui/1.13.2/jquery-ui.min.js - Luxon
https://js.cybozu.com/luxon/3.3.0/luxon.min.js - Spin.js
https://js.cybozu.com/spinjs/2.3.2/spin.min.js - SweetAlert
https://js.cybozu.com/sweetalert/v1.1.3/sweetalert.min.js
- jQuery
- 「適用ファイルの準備」で用意した「grkin_common.js」と「grkin.js」のファイルを追加してください。
設定するファイルおよびリンクの順番には意味がありますので、次の点をご注意ください。- 「grkin.js」のファイル内で、各ライブラリや「grkin_common.js」ファイルの内容を参照している為、各ライブラリや「grkin _common.js」ファイルを先に読み込まなければいけません。
そのため、JavaScriptカスタマイズの適用するファイルおよびリンクの順番は次のようにお願いします。
設定する順番:- 各ライブラリ
- grkin_common.js
- grkin.js
- 「grkin.js」のファイル内で、各ライブラリや「grkin_common.js」ファイルの内容を参照している為、各ライブラリや「grkin _common.js」ファイルを先に読み込まなければいけません。
- Cybozu CDNの次のライブラリを使用しますので、次のリンクを追加してください。
- [CSSカスタマイズ]項目には、下記を設定します。
- Cybozu CDNの次のライブラリを使用しますので、次のリンクを追加してください。
- jQuery UI
https://js.cybozu.com/jqueryui/1.13.2/themes/smoothness/jquery-ui.css - SweetAlert
https://js.cybozu.com/sweetalert/v1.1.3/sweetalert.css
- jQuery UI
- 「適用ファイルの準備」で用意した「grkin.css」のファイルを追加してください。 こちらも設定する順番にはご注意ください。
- Cybozu CDNの次のライブラリを使用しますので、次のリンクを追加してください。
- 上記の設定完了後の完成イメージはこちらです。
同じように設定ができましたら、「設定」をクリックしてください。
以上ですべての設定は完了です!
最初にお見せした完成イメージのとおり、動けば成功です。
お疲れさまでした!
おわりに
今回のサンプルはいかがだったでしょうか?
「grkin.js」というファイルは、変更しないことを前提にサンプルを作成しており、そのファイルの内容は上級者向けです。
そのため、今までのサンプルにはない内容となっています。
エンジニアの方なら、こちらのファイルも修正してみようかな?と思う方もいらっしゃるかもしれません。
GaroonにJavaScriptカスタマイズが追加されて、できることが広がっています。
このサンプルをきっかけにぜひ、いろいろと試していただければと思います。
今後もこのようなサンプルを紹介しますので、また見に来てください。