【パブリッシャー】ハイブリッドレポートを作成してみる

# はじめに #
今回はWEBレポートの応用編を紹介します。
プロンプトとテーブル部分はSharperlight標準のものを使用し、それにカスタムした部分を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/2af28118-4f21-61ae-ecc7-08455313aa44.png)
プロンプトで絞り込んだデータが表示され、そのデータに対してユーザーがなんらかのアクションを起こせるような対話型のWebレポートとなります。
この例では、`Approve`と`Reject`という二つのボタンを用意し、それぞれのボタンが押されればそれに対応した処理を行われるようにJavaScriptで処理を記述します。
# WEBレポートの作成 #
Sharperlightアプリケーションメニューより**パブリッシャー**を起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/a2767f09-5746-2dd2-8d9b-e02197e3307a.png)
`新規`ボタンで新しいWEBレポートの作成を開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/0aec1740-5bfe-18d3-8990-44e552d7d15e.png)
**一般**タブでは、`一意のコード`(グループ+コード)、`タイトル`および`レポートのタイトル`を記入します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/e445acd9-4883-5ae0-bedb-f617ec048dcd.png)
ダイアログ下部の`クエリーを編集`ボタンでクエリの定義を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/4fdde77e-3d63-191d-a3d5-d0b4914c8021.png)
クエリビルダが開きます。
下図のようにSAP Business Oneデータモデルを使用したクエリを作成してみました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/0d1a5905-afec-2ada-df8d-86ca06c7e80a.png)
1. Document Draft Number、このフィルターがプロンプトとして表示される
2. CurrentUserは、JavaScriptの中で使用したいので、プロンプトフィルター（クエリのWHERE句を形成する要素とはならないが、ユーザーに入力を促すことはできる）として定義します。更にプロンプトとして表示させたくないのでHiddenとします
3. テーブルを構成する要素です

`プレビュー`ボタンでクエリの動作確認を行います。上手く動きました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/dc4c0c35-0d4e-d74a-07de-3728bdba8405.png)
`OK`ボタンでクエリを保存、さらにWEBレポートも`OK`ボタンで保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/4f7a56e0-7108-dd05-55ac-fde2040fac07.png)

# カスタムHTML + JavaScriptコードの作成 #
この部分の作成を行います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/ac6f815e-39fe-ccdf-e85a-a7dfb9a8e0e0.png)
HTML + JavaScriptで作成します。Visual Studio Code等のエディタで記述します。
二つのボタンとテキスト領域の定義はこのようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/80b9c96f-ad28-8480-ed2b-3b96d997f175.png)
ボタンが押された時の処理等はこのようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/e043fdeb-0de0-e41a-fc4d-d9491e58cede.png)
1. このコードが埋め込まれるWebレポート（先に作成したWebレポート）のクエリからフィルター情報を受け取る部分。`@Company`や`@DocumentDraftNumber`等は、クエリのフィルター名の参照コードです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/e8581543-a982-8bef-a9e7-d898bd4100d9.png)

2. Sharperlight RESTfulサービスへクエリを送信し、データセットを受け取る部分。これはAprroveやRejectといったボタンが押された場合に、表示されているデータと同じもの＋αをSharperlightクエリで取得し、次のなんらかの処理に渡すためのコードです。例えばSharperlightのWriteback機能を利用して別のテーブルに書き込む等。今回は取得したデータを画面に表示するだけです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/201ae502-11e0-0bf0-bb31-b1b6868d5ecc.png)
3. 2でSharperlight RESTfulサービスへクエリを送信する際の**URLリクエスト構文**を定義しています。パブリッシャーでこのクエリ(Webレポート)も準備します。

コード全てをここに記しておきます。
```
<script>
    var gPublisherGroup = "QiitaSample";
    var gStatus = '';
    var gFilterCompany = "{*Filter[@Company]}".replace(/'/g, "\\'");
    var gFilterDocumentDraftNumber = '{*Filter[@DocumentDraftNumber]}'.replace(/'/g, "\\'");
    var gDatasetLinesURL = '{*Url.Root}DataSource/?query=' + gPublisherGroup + '.Hybrid_ds_Lines' 
                    + '&fltCompany={@@COMPANY}&fltDocumentDraftNumber={@@DOCUMENTDRAFTNUMBER}'
                    + '&dfmt=jsonarray&dcat=UseNames&usid={_System.Rest.Usid}';
    
    // Callback function sets
    var getCallback = {
        success: getRequest_Success,
        failure: getRequest_fail
    };

    
    //===================================
    // Global Variabales
    //===================================
    var gDatasetLines = [];
    var gHead_Comment = "";
    var gLine_Buffer = [];

    function btApprove_click(source, mode){
        try{
            gStatus = mode;
        
            // Keep input value
            gHead_Comment = $.trim($('textarea#ApprovalComment').val());
            
            //====================================================
            // Get approved documents detail
            //====================================================
            var dsUrl = gDatasetLinesURL.replace("{@@COMPANY}", gFilterCompany).replace("{@@DOCUMENTDRAFTNUMBER}",gFilterDocumentDraftNumber);
            console.log(dsUrl);
            Sharperlight_Get(dsUrl, getCallback, "A");
        
        }catch (err){
            console.error('Error on btSubmitAll_click(): ' + err.message);
        }
    }

    function btReject_click(source, mode){
        try{
            gStatus = mode;

            // Keep input value
            gHead_Comment = $.trim($('textarea#ApprovalComment').val());
            
            //====================================================
            // Get approved documents detail
            //====================================================
            var dsUrl = gDatasetLinesURL.replace("{@@COMPANY}", gFilterCompany).replace("{@@DOCUMENTDRAFTNUMBER}",gFilterDocumentDraftNumber);
            console.log(dsUrl);
            Sharperlight_Get(dsUrl, getCallback, "R");
        
        } catch (err){
            console.error('Error on btReject_click(): ' + err.message);
        }
    }


    //====================================================
    // Request datasets
    //====================================================
    var gPostingStatus = "X";
    function Sharperlight_Get(url, cb, status){
        try {
            if (url != ""){
                gPostingStatus = status;
                var request = YAHOO.util.Connect.asyncRequest('GET', url, cb);
            }
        } catch (err) {
            console.error('Sharperlight_Get(): ' + err.message);
        }
    };
    
    // callback functions for header request
    function getRequest_Success(o) {
        console.log("getRequest_Success: " + o.responseText);
        gDatasetLines = JSON.parse(o.responseText);
        
        if (gPostingStatus == "A"){
            if (!confirm('Are you sure you would like to approve all the document(s)?')){
                return;
            }
        }else if(gPostingStatus == "R"){
            if (!confirm('Are you sure you would like to reject all the document(s)?')){
                return;
            }
        }else{
            alert("Posting Status was wrong.");
            return;
        }
        
        if (gDatasetLines.length == 0) { 
            console.log('No document records found or the data already was processed');
            window.parent.location.reload();
            return; 
        }
            
        gLine_Buffer = [];
        for(var r=0; r<gDatasetLines.length; r++){
                var rowItem = {
                    'CompanyBase': gFilterCompany,
                    'InternalNumber': gDatasetLines[r].InternalNumber,
                    'DocumentDraftNumber': gFilterDocumentDraftNumber,
                    'Answer': gPostingStatus,
                    'Authorizer': gDatasetLines[r].Authorizer,
                    'Date': gDatasetLines[r].Date,
                    'Comment': gHead_Comment
                    }
            gLine_Buffer.push(rowItem);        
        }
        
        // Post Remarks to the server
        var message = "";
        if (gLine_Buffer.length > 0){
            for (var l=0; l<gLine_Buffer.length; l++){
                message +="CompanyBase: " +  gLine_Buffer[l].CompanyBase + "</br>InternalNumber: " + gLine_Buffer[l].InternalNumber + "</br>DocumentDraftNumber: " + gLine_Buffer[l].DocumentDraftNumber + "</br>Answer: " + gLine_Buffer[l].Answer + "</br>Authorizer: " + gLine_Buffer[l].Authorizer + "</br>Date: " + gLine_Buffer[l].Date + "</br>Comment: " + gLine_Buffer[l].Comment + (l<gLine_Buffer.length ? "</br></br>" : "");
            }
            kendo.alert(message);
        }
    
    };
    
    function getRequest_fail(o){
        console.error("getRequest_fail: " + o.responseText);
    };

</script>

<table style="width:100%">
    <tr>
        <td style="width:110px;">
            <button id='btApprove' class="button button1" onClick='btApprove_click(this.id, "A")'>Approve</button>
            <button id='btReject' class="button button2" onClick='btReject_click(this.id, "R")'>Reject</button>
        </td>
    </tr>
    <tr>
        <td colspan="2"><textarea id="ApprovalComment" maxlength="1000"></textarea></td>
    </tr>
</table>


<div id="PostMessage" style="display: none">The post result message will be shown here.</div>


<script type="text/javascript">
    
    $(document).ready(function() {
        console.log("Page is ready");
    });

</script>
```
# HTML + JavaScriptコードの埋め込み #
先のHTML + JavaScriptコードをHTMLファイルとして保存します。
最初に作成したWEBレポートの定義を開きます。
**オプション**タブに移動し**リソース**の`編集`ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/760d61a9-f54c-0f56-de0d-20fa8f19a834.png)
リソースダイアログでは`新規`アイコンで新しいリソースの定義を開始します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/e1f196ae-c12a-1d8d-74a7-d9cdbfae8177.png)
次に、パスのファイル検索アイコンで先ほど作成したHTMLファイルを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/84a9bc08-2299-b04c-f4b5-3d61fbb8ac6d.png)
ファイルの指定が完了したら、**参照**の`コピー`ボタンで一意のコードをクリップボードにコピーします。
リソースダイアログを閉じます。
リソースの`編集`ボタンが緑に変わりましたね。これでHTMLファイルがリソースとして添付されました。
ユーザー設定のスタイルの`ヘッダー`チェックボックスをチェックし、`編集`ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/f42631f4-91aa-3b8e-6bb6-969573323929.png)
`No`で先に進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/1404515e-e774-7692-1fe1-cc0cc964798e.png)
カスタムHTMLヘッダーダイアログが開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/39776651-98af-9d62-146b-b843dbfff20b.png)
**ReportContentSectionBegin**と**ReportContentSectionEnd**タグのみを残し、他は削除します。
```
<!--ReportContentSectionBegin-->
<!--ReportContentSectionEnd-->
```
BeginタグとEndタグの間に先ほどコピーしたリソースの参照コードを貼り付けます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/c230c098-154c-bb50-9b25-17182962df9e.png)
`OK`ボタンで保存します。以上で埋め込み完了です。
ヘッダーの`編集`ボタンも緑になりました。`適用`ボタンで変更を保存しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/5ffe0023-e922-14be-4fc0-e8b25f02c8cd.png)

# クラスの定義 #
先に作成したHTML + JavaScriptコードで使用しているCSSを、このWebレポートに定義します。
オプションタブ、ユーザー設定のスタイルの`CSS`チェックボックスをチェックし、`編集`ボタンを押します。
以下のコードを貼り付けます。
```
<style type="text/css">
	body{
		font-family: Monospace;
		font-size: 17px;
	}
	.button {
		margin-bottom: 15px;	
		margin-left: 20px;	
		display: inline-block;
		padding: 8px 25px;
		font-size: 15px;
		cursor: pointer;
		text-align: center;
		text-decoration: none;
		outline: none;
		color: #fff;
		border: none;
		border-radius: 7px;
		box-shadow: 0 6px #999;
	}
	.button1{
		background-color: #2F8272;
		width:	105px;
	}
	.button1:hover {
		background-color: #225e52;
	}
	.button1:active {
		background-color: #225e52;
		box-shadow: 0 5px #666;
		transform: translateY(4px);
	}
	
	.button2{
		background-color: #ED4C32;
		width:	105px;
	}
	.button2:hover {
		background-color: #bc2710;
	}
	.button2:active {
		background-color: #bc2710;
		box-shadow: 0 5px #666;
		transform: translateY(4px);
	}

	textarea {
		width: 650px;
  		height: 75px;
  		padding: 8px 8px;
  		box-sizing: border-box;
  		border: 2px solid #ccc;
  		border-radius: 4px;
  		background-color: #f8f8f8;
  		font-size: 16px;
  		resize: vertical;
	}
	#PostMessage{
		position: relative;
    		top: -6px;
    		margin-left: -40px;
	}
	.reviewedByLabel{
		display: inline-block;
    		margin-left: 15px;
    		position: relative;
    		top: -6px;
	}
	.reviewedBy{
		display: inline-block;
    		position: relative;
    		top: -6px;
		width: 200px;
	}
	.inspactor{
		width: 200px;
		height: 25px;
	}
	.inspactorValidationError{
		background-color: #eb8584;
	}
	.originalTotalLabel{
		display: inline-block;
    		margin-left: 15px;
    		position: relative;
    		top: -6px;
	}
	.originalTotal{
		display: inline-block;
    		position: relative;
    		top: -6px;
	}
	.newTotalLabel{
		display: inline-block;
    		margin-left: 15px;
    		position: relative;
    		top: -6px;
	}
	.newTotal{
		display: inline-block;
    		position: relative;
    		top: -6px;
	}
	#dash1Z{
		position: absolute;
		left: 888px;
	}
</style>
```
このような感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/b5ed1a0d-ad46-37c3-d22e-ad6b45321468.png)
`OK`ボタンで保存します。
CSSの`編集`ボタンも緑になりました。`適用`ボタンで変更を保存しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/fede087d-2a97-751a-1dfe-1f9f50c29b6a.png)

# テーマの設定 #
最後に表示されるテーブルの容姿をすこし綺麗にします。
オプションタブ、ユーザー設定のスタイルの`テーマ`チェックボックスをチェックし、`編集`ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/d0551704-bf62-e837-d985-2bde575ddb8a.png)
開いたテーマダイアログのURLパラメターテキストボックスに以下のコードを貼り付けます。
```
&style=tmp2
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/90ae902b-4a66-3bcc-f30f-57b9cb3e055e.png)
`OK`ボタンで設定を保存します。
テーマの`編集`ボタンも緑になりました。`適用`ボタンで変更を保存します。更に`OK`ボタンでWebレポートの定義を閉じます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/7a87ffc4-fa30-3b7d-4687-6051de189778.png)

以上でハイブリッドWebレポートの作成は終了です。

# 動作確認 #
では、早速動作確認といきましょう。
WidnowsサービスあるいはSharperlightアプリケーションメニューよりSharperlightサービスを起動します。
パブリッシャーのレポート一覧より先に作成したWebレポートを選択し、右クリックメニューから`レポートの表示`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/9c9d7b5f-3037-ab26-cc37-21ce6a337204.png)
お手持ちのブラウザでそのWebレポートが開きます。
`Document Draft Number`フィルターは空白のまま、`Submit`ボタンでクエリを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/f6bb787a-f8f0-af64-389a-7f55996a3af5.png)
テーブルがHTML + JavaScript部分と共に表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/8e3d5595-2c00-d66f-83b1-abc0c24bce17.png)
`Approve`ボタンを押すと確認メッセージが表示され、`OK`を押して次の情報が表示されれば正常です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/8328b2a1-9311-d18f-2be5-070c26852d2b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2530698/166f576c-f2c4-d7a6-a833-dc2d485f5a4a.png)

# 最後に #
このようにデータソースへのアクセス部はSharperlightにお任せで、その他の部分の拡張に全力を注げそうですね。