Vuex で フォームのバリデーションを実装するにはデータバインディングを使わない方がいい

データバインディングに依存したフォームのバリデーションは非同期な処理が入ったり、他のフォームのフィールドにアクセスする必要があると途端にコードが複雑になってしまうのでどうしたものかと試行錯誤した結果、データバインディングを使わなければコードもシンプルかつ低負荷な実装ができるという結論に至った。

フォームに送信するまではバリデーションを行わず、フォームを送信後はリアルタイムにバリデーションを行う例で考えてみる。

.vue 側はこのように書く。


<template>
...
<input type="text" class="form-control"
               :value="form.params.username.value"
               @keyup.stop="update(
                    value: $event.target.value,
                    )”
<span v-if=“form.params.username.error.length > 0”>{{form.params.username.error}}</span>

<button @click=“submit”>submit</button>
...
</template>

<script>

export default {

    methods: {
        update(value){
            this.$store.dispatch(‘updateUserName',value)
        },
        submit(){
            this.$store.dispatch(‘submit’);
        }
    }
}


</script>

上のコードは input タグが文字入力を受け付けるたびに updateUserName というメソッドに input タグの値を引数にして呼び出している。v-model を使用していないのがポイント。

Vuex 側はこのように書く。



state: {
    form: {
        submitted: false,
        params: {
            username: {
                value: ‘’,
                error: ''
            }
        }
    }
}

mutations: {
    validateUserName(state,field){
        if (field.value.trim().length === 0){
            field.error = 'ユーザー名を入力してください’;
        }else{
            field.error = ‘';
        }
    },
    updateUserName(state,payloads){
        // username をセット
        state.params.username.value = payloads.value;
        // submit がまだの時はバリデーションを行わない
        if (!state.submitted){
            return;
        }
        // username のバリデーションを行う
        this.commit(‘validateUserName’,state.params.username);
    },
    submit(state){
        state.submitted = true;
        this.commit(‘validateUserName’,state.form.params.username);
        if (state.form.params.usernname.error === '’){
            // success!
        }else{
            // validation error.
        }
    }
},

actions: {
    udpateUserName(value){
        const payloads = {value};
        if (value.trim().length === 0){
            this.$store.commit(‘updateUserName',payloads);
        }
    },
    submit(){
       this.$store.commit('submit');
    }
}


Vuex 側では 2つの処理を行なっている。

(1) username の値が変化すると, username のバリデーションを行い、username に値をセット。
(2) submit が呼ばれると, submitted フラグを有効にして username のバリデーションを行なっている。

データバインディングを使ったバリデーションよりも処理の流れが細やかに制御することができるし、処理内容も明確になった。

[ Python ] AWS SES を使ってメールを送信する

AWS の SES を使ってメールを送信するコードのメモ。


from boto3 import Session

BODY = “””
<html>
<head>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>”””


TITLE = ’Title'

class SESMailer(object):
    def __init__(self, from_email,emails):
        self.from_email = from_email
        self.client = 
        self.emails = emails

   def get_client():        
        return Session(aws_access_key_id=‘xxx',
               aws_secret_access_key=‘xxx',
               region_name='us-east-1’).client(’ses’)

   
    def send(self):
        try:
            response = self.client.send_email(
                Destination={
                    'ToAddresses': self.emails,
                },
                Message={
                    'Body': {
                        'Html': {
                            'Charset': CHARSET,
                            'Data': BODY
                        },
                    },
                    'Subject': {
                        'Charset': “UTF-8",
                        'Data': TITLE,
                    },
                },
                Source=self.from_email,
            )
            print(“メールの送信を行いました”)
            return response
       except Exception as e:
           print("メールの送信に失敗しました”)
            raise Exception('Could not send Email')


使い方

from_email = ‘from@gmail.com’ # 送信元のメールアドレス
addrs = [‘to1@gmail.com’,’to2@gmail.com’] # 送信先のメールアドレス一覧
emailer = SESMailer(client,from_email,addrs)
emailer.send()

[ JavaScript ] 連想配列を複数の要素で並び替える

例えば objects をid,年齢の昇順に並び替える実装を Vanilla JavaScript で行なった場合は以下のようになります。

const objects = [
    {id: 1, name: 'foo', age: 22},
    {id: 3, name: 'foobar', age: 20},
    {id: 2, name: 'foobar', age: 24},
    {id: 2, name: 'foobar', age: 30},
    {id: 2, name: 'bar', age: 20},
];

const compareId = (a, b) => {
    if (a.id > b.id) {
        return 1;
    } else if (a.id === b.id) {
        return 0;
    } else {
        return -1;
    }
};
const compareAge = (a, b) => {
    if (a.age > b.age) {
        return 1;
    } else if (a.age === b.age) {
        return 0;
    } else {
        return -1;
    }
};


const compare = (funcs, a, b) => {
    for (let func of compareFuncs) {
        if (func(a, b) === 1) {
            return 1;
        } else if (func(a, b) === -1) {
            return -1;
        }
    }
    return 0;
};


const wrappedCompare = (funcs, a, b) => {
    return compare(funcs, a, b)
};

const compareFuncs = [compareId, compareAge];
const sortedObjects = objects.sort(wrappedCompare.bind(this, compareFuncs));
console.log(sortedObjects);

出力結果は以下

[ { id: 1, name: 'foo', age: 22 },
  { id: 2, name: 'bar', age: 20 },
  { id: 2, name: 'foobar', age: 24 },
  { id: 2, name: 'foobar', age: 30 },
  { id: 3, name: 'foobar', age: 20 } ]

ちなみに lodash の _.sortBy 関数を使うともっと簡潔に書くことができます。

const _ = require('lodash');

const objects = [
    {id: 1, name: 'foo', age: 22},
    {id: 3, name: 'foobar', age: 20},
    {id: 2, name: 'foobar', age: 24},
    {id: 2, name: 'foobar', age: 30},
    {id: 2, name: 'bar', age: 20},
];


const compareAge = (obj) => {
    return obj.age;
};

const compareId = (obj) => {
    return obj.id;
};


const sortedObjects = _.sortBy(objects, [compareId, compareAge]);
console.log(sortedObjects);