meieiのブログ

仕事や日々の備忘録。

AngularJSのUI-Grid自分用まとめ

久々の投稿。
UI-Gridには日本語ドキュメントがないし日本語記事も少ない、どころか英語でも記事が少ないので公式ドキュメントを翻訳機能に頼りながら(いい加減英語学べよって話だけど)読み解いていくしかなく、めちゃくちゃ苦しめられました。
そのためいつかやったことをまとめて書きたいと思いながら半年くらい経ちました。(もっとかもしれない)

結果色々忘れてしまったので、そんなにまとめられないけども、覚えてる限りをまとめておきます。
間違いがあるかもしれません。何かあればお知らせください(コメント誰でもできるんでしたっけ?) 

通常の使い方

まずは基本。

HTML

<div ui-grid="myGrid" ui-grid-pagination ui-grid-edit ui-grid-cellnav></div>

js

angular.module('myApp', [ 'ui.grid', 'ui.grid.pagination', 'ui.grid.edit', 'ui.grid.cellNav'])
  .controller('MyController', ['$scope', 'uiGridConstants', function($scope, uiGridConstants) {
    $scope.myGrid = {
      columnDefs: [
        {
          field: 'name',
          displayName: '名前'
        },
        {
          field: 'age',
          displayName: '年齢'
        },
        {
          field: 'gender',
          displayName: '性別'
        },
        {
          field: 'birthday',
          displayName: '誕生日'
        }
      ],
      data: [
        {
          'name': ありさ,
          'age': 23,
          'gender': female,
          'birthday': 19971018,
        },
        (略)
      ]
    };
  }
]);

リンク埋め込み

cellTemplateに直接HTMLを記述する(変数に入れてからでも)ことでリンクに限らず様々な表現が出来る。
 この時、データのカラム名をnameに設定しているのを前提として、
{{COL_FIELD}}はまさに表示しているカラムの情報になり、
{{row.entity.カラム名}}とすると、同じ行の別のカラムの情報を表示できる。  

cellTemplate: `<div class="ui-grid-cell-contents">
              <a href="#/URL_PREFIX/{{ COL_FIELD }}">{{ COL_FIELD }}</a>
            </div>` 

ボタン埋め込み

埋め込み方はリンクと同じ。
クリックイベントを設定したいときは$scope.hogehogeに関数を定義してng-click="grid.appScope.hogehoge(row.entity)"のように渡す。
(AngularJSでHTMLにクリックイベントを記述する際にvm.hogehoge関数を定義する例が多い気がするがそれだと渡せない)
通常のController.jsとui-gridのスコープが異なり、grid内になると別スコープになるため、vm.などで定義している変数に直接はアクセスできないらしい。

フォーカスインのイベント

フォーカスインではなく「フォーカスが移動したら」というイベントを拾ってその中で判断することで実現できる。
以下を$scope.gridOptions.onRegisterApiに渡しているFunctionの中に入れる。

// cell移動時に処理を行う
gridApi.cellNav.on.navigate($scope, function(newRowcol, oldRowCol) {
  console.log('cellNav.on.navigate'); 
  console.log('new col', newRowcol);
  if (newRowcol.col.colDef.name === 'カラム名') {
    console.log('name is ', newRowcol.col.colDef.name);
    // ここでさらに値の判定とその後の処理
  }
}); 

バリデーションを任意のタイミングで実行

UI-GRIDではバリデーションを設定しているとセルのフォーカスアウトのタイミングでバリデーションが動作するが、フォーカスを当てずにプログラムで自動で番号を振ったり、などしたときはバリデーションが動作しない。そのため、任意のタイミングでバリデーションを動作させたいときは以下のような記述が必要。

var rowsRendred = $scope.gridApi.grid.renderContainers.body.renderedRows;
rowsRendred.forEach(function (row) {
  row.grid.options.columnDefs.forEach(function (colDef) {
    $scope.gridApi.grid.validate.runValidators(row.entity, colDef, row.entity[colDef.field], NaN, $scope.gridApi.grid); 
  });
});  

オリジナルのバリデーション

UI-Gridはバリデーションをいくつか用意しているけど、オリジナルのバリデーションを使いたい場面はよくあるはず。
例えば数値チェック。

uiGridValidateService.setValidator('numValidate', validateFunction, messageFunction);
// uiGridValidateServiceはController.jsに注入が必要

// Validator用の関数
function validateFunction(argument) {
  // 正規表現:実数
  var regExpStr = '^[-]?\\d+(?:\\.\\d+)?$'; 

  // 小数は不許可
  if (argument && !argument.decimal) {
    regExpStr = '^[-]?\\d+$';
  }
   var regexp = new RegExp(regExpStr);
   return function(newValue, oldValue, rowEntity, colDef) {
    if (!newValue) {
      rowEntity.errors[colDef.name] = false;
      return true;
    } else {
      var result = regexp.test(newValue);
      rowEntity.errors[colDef.name] = !result;
      return result;
    }
  };
}

function messageFunction(argument) {
  // エラーメッセージ
  var message = '数値を入力してください';
   if (argument && !argument.decimal) {
    message = '整数を入力してください';
  }
  return message;
}

これでオリジナルのバリデーターができたのでColumnDefsのvalidatorsに設定。

columnDefs: [ 
  {
    name: 'data1',
    displayName: 'データ1',
    validators: {
      numValidate: { decimal: true }
    }
  },
  ...
],

セルのテンプレートを編集時と参照時で分けたい

今回は時刻を扱いたかったが、UI-GRIDはcellTemplateにnumberだったりdateだったりいろいろとあるものの、timeだけ、というのはなさそう。
調べたところ基本的にcellTemplateにHTML書けば動くよ~な情報ばかりで、
cellTemplate: '<div><input type="time"><div>'
のように試したところ、ui-gridの機能で編集用のテキストボックスが出てきてしまいinputでの入力がうまくいかず…   さんざん調べたところ、editableCellTemplateを指定できる!!!

editableCellTemplate: '<div><input type="time"><div>'として、
表示するときにはcellTemplateやcellFilterを用いて形式を指定することで入力時にはinput要素、表示時には文字だけ、とできました。

行のヘッダーをつける

HTMLのテーブルで行のヘッダーを付ける場合、

<tr ng-repeat="data in vm.tableData">
  <th>{{data.title}}</th>
  <td>{{data.data1}}</td>
  <td>{{data.data2}}</td>
  <td>{{data.data3}}</td>
</tr>

でうまいこと出来るやつをUI-Gridでもやりたい。

UI-GRIDの場合はcolumnDefsの中に他のカラムと同様に行ヘッダーも入れ込むのではなく
$scope.gridApi.core.addRowHeaderColumnで行ヘッダーのカラムを追加してあげる必要があるらしい。

$scope.gridOptions = {
  data : data,
  columnDefs: [ 
    { name: 'data1', displayName: 'データ1' }{ name: 'data2', displayName: 'データ2' },
    { name: 'data3', displayName: 'データ3' }
  ],
  onRegisterApi: function( gridApi ) {
    $scope.gridApi = gridApi;
    $scope.gridApi.core.addRowHeaderColumn({
      name: 'rowHeaderCol',
      displayName: '',
      cellTemplate: `<div class="ui-grid-row-header-cell ui-grid-disable-selection">
                      <div class="ui-grid-cell-contents">{{row.entity['rowHeader']}}</div>
                  </div>`
    });
  }
}

以上。

参考:公式ドキュメント http://ui-grid.info

Python(boto3)でAWS S3に進捗表示させながらファイルアップロードする

AWS SDK for Python(boto3)を使用してS3にファイルをアップロードする方法は調べたらすぐ出てくるものの、進捗表示と組み合わせるとなるとなかなか情報が少なかったので(調べ方が下手なだけかもしれないけど)、試行錯誤した結果を記録しておこうと思います。

今回はupload_fileを使います。

早速コード
様々な処理の合間に書いてたコードを抜粋しただけなので、これだけの動作確認はしてません。

import threading
import os
import boto3

class FileUploadTestClass:
    def __init__(self):
        super().__init__()

        self._filepath = '/%PATH%/test.txt'
        self._filesize = float(os.path.getsize(self._filepath))
        self._transfer_size = 0
        self._lock = threading.Lock()

    def file_upload_test(self):
        s3_bucket_name = 's3_bucket_name' # S3のバケット名
        s3_uploadfile_path = 'test/test.txt' # アップロード先のパス

        s3 = boto3.resource('s3')
        s3_bucket = s3.Bucket(s3_bucket_name)
        s3_config = boto3.s3.transfer.TransferConfig(
            use_threads=True,
            max_concurrency=10,
        )

        try:
            # ファイルアップロード
            s3_bucket.upload_file(
                Filename=self._filepath,
                Key=s3_trainingdata_path,
                Callback=self,
                Config=s3_config,
            )
        except boto3.exceptions.S3UploadFailedError:
            # ここでエラー時の処理
            pass

    def __call__(self, bytes_amount):
        with self._lock:
            self._transfer_size += bytes_amount
            percentage = (self._transfer_size / self._filesize) * 100

            # 進捗をコンソール出力
            print('{0}  {1} / {2} ({3}%)'.format(self._filename, self._transfer_size, self._filesize, percentage))
            # ここでログファイルに書き込んだりetc

upload_fileのCallbackには、クラスを指定します。
今回はselfにしたけど、別のクラスを作成して指定しても良いです。
ここで指定したクラスの、__call__関数が呼ばれます。__call__はクラス名を指定すると呼ばれる関数、だったかな…。
引数にbytes_amount
これはその時転送したバイト数なので、どんどん足していくことで今までに何バイト転送できたかが分かります。

ちなみにupload_fileのCofigには事前にboto3.s3.transfer.TransferConfigで作成した設定を入れます。
今回のコードでは
use_threads=True,
max_concurrency=10,
として、10スレッドで平行に処理させてアップロード速度アップさせてます。
このスレッドにはmainスレッドも含まれるので注意。アップロード中に他のことやりたかったら、ファイルアップロード処理自体をスレッドクラスのrunの中でやるとかしなきゃいけないです。

以上。

参考: 公式ドキュメント
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.upload_file

https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-uploading-files.html

Pythonのスレッドで引数を渡せなくて躓いた話。

初投稿です。

完全に自分用の備忘録です。読みにくさはご勘弁を。(しかも手元にiPhoneしかない。コードをフリック入力とか初めてです)


ここ3ヶ月ほどPythonを触ってるものの、未だに訳が分かっていない中、最近はスレッドをやりました。
普段はクラスを使ってこんな感じ。

import threading

class MyThread(threading.Thread):
    def __init__(self, aaa):
        super().__init__()
        self._aaa = aaa

    def run(self):
        # 処理
        print(self._aaa)

スレッドのスタートは

thread_1 = MyThread(aaa)
thread_1.start()

で問題なくできました。完璧です。


関数でやるときは…

# クラス内の想定
def my_thread(self, aaa):
    # 処理
    print(aaa)

レッドスタートは…

thread_1 = threading.Thread(target=my_thread, args=aaa)
thread_1.start()

としたところエラーが。

TypeError: my_thread() takes 2 positional arguments but 15 were given

15って何?!?!

aaaで渡そうとしていたのは14文字の文字列…
1つはselfだから…残りの14は文字列が1文字ずつ分割されて渡されてる…?
で、今更ながらスレッド処理を調べました。
使う前に調べなきゃいけないですね。

詳しい説明はもう覚えてないので改めて調べるのはまた今度にするとして省きますが、argsは引数をタプルとして関数に渡すんだとか。

args=(aaa,)
とするのが正解でした。
全体としては

thread_1 = threading.Thread(target=my_thread, args=(aaa,))
thread_1.start()


たったこれだけのことに、ちゃんとドキュメント見てやってないから躓いてました。
ドキュメント見たところで私の注意力じゃ見逃しそうですけどね。

こんなことで躓きつつも、
ここ半年でPythonC#JavaJavaScriptとちょこちょこ触って必死にコード書いてる新人の初投稿でした。

P.S. 既に誤字脱字を見つけては3回ほど編集しております。注意力散漫です。まだ誤字脱字あるかもですがご勘弁を…