😎

WixStudioでVeloを使う Step 21 「会員情報(Member)を使い始める/サイト会員同士のメッセージ機能(?)を作成する」

2023/09/28に公開

はじめに

会員情報を使ってみる。会員情報を使えば、ウェブサイトで提供できるサービスの幅も広がる。はず。WixStudioで作成したサイトに会員の仕組みを導入することは簡単。会員登録もログインもマイページも秒で実現できる。あとはVeloで会員の情報を扱えれば面白いことが出来るかもしれない。

目的

会員管理の仕組みをサイトに導入して、会員情報をVeloで操作してみる。例えばメッセージ送信的なモノを作ってみる。

準備

0から始めるので、前提は無し。

Wix会員エリアをインストールする

Wixではウェブサイトでよく使われる仕組みがアプリとして用意されている。例えば会員サイトの仕組みなども「Wix会員エリア」として提供されている。0から作ったらとてつもなく時間がかかるものなので非常にありがたくて便利。他にも、ネット通販やコミュニティなどの仕組みも提供されてる。

WixAppMarketからアプリを探す

WixStudioの左サイドバーに「AppMarket」ボタンが用意されている。そこから目的のアプリを探せばいい。

Wix会員エリアをインストールする。

WixAppMarketで「会員」をキーワードに探す。

WixAppMarketで「Wix会員エリア」を探す
WixAppMarketで「Wix会員エリア」を探す

結果から「Wix会員エリア」を選択すれば詳細が確認できる。

「Wix会員エリア」の詳細
「Wix会員エリア」の詳細

「サイトに追加」ボタンを押せば、必要な要素が自動で構築される。

「Wix会員エリア」をインストール中
「Wix会員エリア」をインストール中

インストールが完了すると、メンバーページがWixStudioに表示された。

「Wix会員エリア」のインストールが完了
「Wix会員エリア」のインストールが完了

メンバーページ以外にも「新規登録(デフォルト)」や「ログイン(デフォルト)」などのページも作成された。自動で作成されるのはページだけじゃない。コレクションなども自動で作成される。VeloとCMSを有効にして確認する。

「Wix会員エリア」に必要なコレクションも作成される
「Wix会員エリア」に必要なコレクションも作成される

Wix会員エリアによって用意されたコレクションは、「Wixアプリコレクション」に列挙される。多分、アプリの提供元が「Wix」ならそれらのコレクションはWixアプリコレクションに列挙されるのかも。さらに、コレクションはアプリ毎にセクションで分けて管理される。今回インストールしたWix会員エリアは「Members」セクション。だから、「Wixアプリコレクション」の「Members」セクションのコレクションに今回は注目する。

ここで作成されたコレクションのフィールドやパーミッションの変更はできない。基本的には参照するのみ。これらのコレクションの中で、会員情報を保持しているコレクションはPrivateMembersDataFullDataPublicData。正直これらの違いを見つけるのは難しかった。

PrivateMembersData

個人的にはコレが使いづらい(?)。まず、個人の情報として保持している情報が少ない。必要最低限の情報だけを管理している感じ。PrivateMembersDataで管理されているデータのほとんどが、FullDataPublicDataで確認することが出来る。また、参照できるのはサイトの管理者か、会員当人のみ。サイト管理者以外がwixDataQuery.find()を実施した場合、自身の情報だけがヒットする。wixData.get()の場合はまた違う。てっきり会員IDを指定したとして他会員の会員情報は確認出来ないと思い込んでたけどしっかりとヒットする。ただし参照できるフィールドには違いがあるっぽい。
https://dev.wix.com/docs/develop-websites/articles/wix-apps/wix-members/wix-members-private-members-data-collection-fields

FullData

PrivateMembersDataが管理している個人情報に加えて、プロフィール情報やダッシュボードから追加したカスタムフィールドの情報も保持する。3つのコレクションの中で一番情報を保持しているのがコレ。データを参照するとき、サイトの管理者は全てのデータを参照できる。一般会員はプロフィールが公開状態のデータだけを参照できる。wixDataQuery.find()の挙動はプロフィールが公開されているアイテムだけがヒットする。wixData.get()の場合はやはりヒットする。
https://dev.wix.com/docs/develop-websites/articles/wix-apps/wix-members/wix-members-full-data-collection-fields

PublicData

プロフィールを公開している会員情報のみが保持されている。コレクション名通り。だから、全ての会員情報を保持してるわけじゃない。プロフィールの公開状態による。サイト管理者も一般会員も参照できる情報に差が無い。
https://dev.wix.com/docs/develop-websites/articles/wix-apps/wix-members/wix-members-public-data-collection-fields

3つのコレクションはIDフィールドに特徴がある。同一会員の情報はどのコレクションにおいても同じIDが使われる。

サイト会員のプロフィールを設定する

サイト会員のプロフィール公開を設定する。今作っているメッセージ送信の仕組みではPublicDataコレクションを利用して実装するつもり。PublicDataはプロフィールが公開された会員の情報が管理される。プロフィールを公開する操作ができなければ何も始まらない。
ダッシュボードからサイト会員を開く。

ダッシュボードからサイト会員
ダッシュボードからサイト会員

サイト会員のプライバシーを設定したい。

サイト会員のプライバシーを設定
サイト会員のプライバシーを設定

設定箇所は2つ。プロフィールを公開できるサイト会員公開サイト会員プロフィールを閲覧できるユーザー。それぞれ「サイト会員」に許可させる。設定後「保存」ボタンを押す。

プロフィールを公開できる会員/公開サイト会員プロフィールを閲覧できるユーザ
プロフィールを公開できる会員/公開サイト会員プロフィールを閲覧できるユーザ

設定はこれで終わり。

画面を準備する

ページを作成する。ページ名はsendMessage

sendMessageページを作成
sendMessageページを作成

画面には4つの要素を配置する。

配置した4つの要素
配置した4つの要素

配置した要素の種類とIDはこんな感じ。

要素の種類 要素ID 備考
ドロップダウン #dropdown1 送信相手を選択する目的で配置。選択肢は空。
テキストボックス #textBox1 相手に送信するメッセージ本文を入力する目的で配置。
ボタン #button1 送信ボタン
テキスト #text4 案内を表示する。

ドロップダウンの選択肢には、会員が列挙される予定。会員を列挙する処理はVeloで実装する予定。だから、ドロップダウンの選択肢は全て消して良い。

ドロップダウンは空にした
ドロップダウンは空にした

メッセージの送信機能は会員限定にしたいので、アクセス許可をサイト会員限定に変更しておく。

sendMessage画面はサイト会員限定
sendMessage画面はサイト会員限定

最後に、動線を確保するためメニューに追加する。

メニューに追加
メニューに追加

コレクションを準備する

コレクションを作成する。コレクション名はMessages

Messagesコレクションを作成する
Messagesコレクションを作成する

3つのフィールドを作成する。また、Titleフィールドは使わないので非表示。プライマリフィールドはBodyフィールドに設定した。

Messagesコレクション
Messagesコレクション

フィールド名 フィールドキー フィールドタイプ 備考
Body body テキスト プライマリフィールドに設定する
ToMembmer toMember 参照 参照先コレクションはPublicData
FromMembmer FromMember 参照 参照先コレクションはPublicData

参照先コレクションをFullDataにするかPublicDataにするか悩んだ。

ToMemberフィールド
ToMemberフィールド

最後に権限を変更する。閲覧と追加に関する権限を「サイト会員」に変更した。

Messageコレクションの権限設定
Messageコレクションの権限設定

スクリプトを書く

バックエンドコードとページコードを編集する。

バックエンドコード:Messages.jsw

メッセージを送信する処理を関数として定義する。メッセージを送信と言ってもコレクションにデータを登録するだけの処理。

backend/Message.jsw
import wixData from 'wix-data';
import { currentMember } from 'wix-members-backend';

export async function sendMessage(messageObj){

    const me = await currentMember.getMember()
    .then( member => member )
    .catch( error => {
        console.log(error)
        return false
        })
    
    if(!me) return false
    
    messageObj.fromMember = me

    return wixData.save('Messages', messageObj)
    .then( result => true)
    .catch( error => {
        console.log('failed:', messageObj)
        return false
    })

}

コレクションにデータを登録する処理になるのでwix-dataをインポートする。また、送信者の情報は送信処理の直前に取得したいのでwix-members-backend.CurrentMemberもインポートする。
メッセージの送信を行う関数sendMessage(messageObj)を定義する。sendMessage関数は引数を一つ受けとる。messageObjとは送信相手送信内容(と送信者)をまとめたオブジェクト。ただし、送信者情報は関数内でログイン中の会員情報が指定される。

messageObjの構造
{
  toMember: /* 送信相手の会員ID */,
  fromMember: /* 送信元(自分)の会員ID */,
  body: /* メッセージ本文 */,
}

const me = await currentMember.getMember()でログイン中の会員情報を取得。失敗したらconst mefalseとなる。もしconst mefalseだった場合、関数は処理を終了する(if(!me) return false)。取得出来た会員情報をメッセージの送信者情報としてセットする(messageObj.fromMember = me)。messageObjの内容(toMember,fromMember,body)が揃ったら送信処理を実行する。送信処理はMessagesコレクションにアイテムを格納する処理(wixData.save('Messages', messageObj))。Messagesコレクションへの登録結果をtrueorfalseとして、関数sendMessageの戻り値にする(return)。

messageObjを作成し、sendMessage()を呼び出せばメッセージが送信される。

ページコード:sendMessage.###.js

sendMessage.###.jsでは大きく分けて2つの処理がある。

  1. ドロップダウンに会員情報を列挙する。
  2. 入力内容を元に送信内容をまとめる。送信する。
pages/sendMessage.###.js
import wixData from 'wix-data';
import {sendMessage} from 'backend/Messages'

$w.onReady(async function () {

    wixData.query('Members/PublicData').find()
        .then(results => {
            const selectItems = results.items.map(item => {
                return {
                    'label': item.nickname,
                    'value': item._id
                }
            })
            $w('#dropdown1').options = selectItems
        })

    $w('#button1').onClick(evnet => {

        const messageObj = {}
        messageObj.toMember = $w('#dropdown1').value
        messageObj.body = $w('#textBox1').value

        sendMessage(messageObj)
        .then(isSuccess => {
            if(isSuccess){
                $w('#dropdown1').value = null
                $w('#textBox1').value = null
                $w('#text4').text = 'メッセージが送信されました。'
            }else{
                $w('#text4').text = 'メッセージが送信できませんでした。'
            }
        })

    })
});

ドロップダウンに会員情報を列挙する。

会員情報はMembers/PublicDataコレクションから取得する。Wixアプリコレクションのコレクションを利用する際には、セクション名/コレクション名で指定しないといけないので注意(wixData.query('Members/PublicData').find())。取得した会員情報の id(_id)Nickname(nickname) を取り出し、オブジェクトにまとめる。このオブジェクトにはlabelvalueの持たせる。label属性にはnicknameの値を設定する。value属性には _idの値を設定する。

return {
  'label': item.nickname,
  'value': item._id
}

会員情報のIDとNicknameで構成されたオブジェクトを会員分作成し配列化する(const selectItems = results.items.map(item => { /* オブジェクト作成 */}))。配列はconst selectItemsに格納される。この配列をドロップダウンのoptions属性にセットすれば、ドロップダウンの選択肢が完成する($w('#dropdown1').options = selectItems)。

入力内容を元に送信内容をまとめる。送信する。

送信ボタンが押されたときに送信手続きをするのでイベントを定義する($w('#button1').onClick(evnet => { /* 送信手続き */ }))。具体的な送信手続きは2つ。

  1. 入力欄の値を元にメッセージオブジェクトmessageObjを作成する。
  2. 関数sendMessage(messageObj)を呼び出す。

入力欄の値を元にメッセージオブジェクトmessageObjを作成する。

空のオブジェクトを作成し、属性に値をセットすれば良い。

const messageObj = {}
messageObj.toMember = $w('#dropdown1').value
messageObj.body = $w('#textBox1').value

属性はtoMemberbody。この属性名は関数sendMessage()を作成する際に勝手に決めた属性。WixやVeloのルールじゃない。

関数sendMessage(messageObj)を呼び出す。

作成したmessageObjを引数にして、関数sendMessage(messageObj)を呼び出せば良い。

sendMessage(messageObj)
  .then(isSuccess => {
    if(isSuccess){
      $w('#dropdown1').value = null
      $w('#textBox1').value = null
      $w('#text4').text = 'メッセージが送信されました。'
    }else{
      $w('#text4').text = 'メッセージが送信できませんでした。'
    }
  })

送信処理(Messagesコレクションへの登録処理)が成功すればTrue、失敗すればFalseが返される。成功した場合、各入力欄の中身を空にする。成功失敗に限らず、どちらの場合も案内を残す($w('#text4').value = '案内内容')。

一度動作確認する

ここまでの処理だけでメッセージの送信が完了出来る。サイトを公開する。

サイトを公開
サイトを公開

メニューから「sendMessage」を選択する。sendMessage画面はサイト会員限定なので会員登録を促される。

サイト会員登録
サイト会員登録

適当にメールアドレスを入力し、会員登録を完了させる。
出来れば、会員登録は2名分しておいた方が良い。メッセージの送信相手がいないと面白くない。

sendMessage画面
sendMessage画面

2つのフィールドを埋める。

sendMessage画面に入力
sendMessage画面に入力

プルダウンには送信相手の会員を選択。テキスト入力にはメッセージ本文を入力する。最後に「Send」ボタンを押す。

送信完了
送信完了

送信が完了したので、各入力欄は空になる。案内メッセージも「メッセージが送信されました。」と表示される。
Messagesコレクションも確認する。

Messagesコレクション
Messagesコレクション

このメッセージ機能は「送信」と言いつつ、実際はMessagesコレクションへの「登録」に過ぎない。
ToMemberとFromMemberにそれぞれ、送信先メンバー(会員2)と送信元メンバー(会員1)がセットされているのが確認出来た。

画面を改造する。

メッセージの送信だけでじゃなくて、送受信された内容も見られるようにしたい。

リピータを配置する

画面にリピータを配置する。さらに、リピータにはテキスト要素を3つ配置しておく。

リピータを配置
リピータを配置

要素の種類 要素ID 備考
リピータ #repeater1
テキスト #text5 送信先を表示する予定
テキスト #text6 送信元を表示する予定
テキスト #text7 メッセージ本文を表示する予定

スクリプトを追加する

バックエンドコードとページコードを修正する。
バックエンドコードには、自分に関連するメッセージを取得するための関数getMyMessages()を定義する。

backend/Messages.jsw(追記箇所のみ)
export async function getMyMessages(){
    const me = await currentMember.getMember()

    return wixData.query('Messages')
      .eq('fromMember',me._id)
      .include('fromMember')               // <-- POINT(2)
      .include('toMember')                 // <-- POINT(2)
    .or(                                   // <-- POINT(1)
        wixData.query('Messages')
        .eq('toMember',me._id)
        .include('fromMember')             // <-- POINT(2)
        .include('toMember')               // <-- POINT(2)
    )
    .descending('_createdDate')
    .find().then( results =>{
        return results.items
    }).catch( error => [])
}

メッセージの取得はMessagesコレクションからのアイテム抽出になる。抽出条件でポイントになるところが2点。
1つ目は.or()。自身に関係するメッセージを抽出するにはtoMemberもしくはformMemberのいずれかに自身の会員IDがセットされていることが条件になる。だから「もしくは」を示す.or()で2つの条件を指定することになる。
2つ目は.include()toMemberfromMemberは参照フィールド。しかし、フィールドには参照先アイテムのIDが入っているだけ。それでは不便なので該当するアイテム情報をオブジェクトとして取得するために必要な指定。.include()の引数にはフィールド名を指定すれば良い。toMemberfromMember.include('fromMember').include('toMember')で指定し、該当する会員情報(のオブジェクト)を取得する。

https://www.wix.com/velo/reference/wix-data/wixdataquery/or
https://www.wix.com/velo/reference/wix-data/wixdataquery/include

次にページコードを修正する。
ページコードには、関数getMyMessagesで得られた情報を元に、リピータへの表示処理を記載する。

pages/sendMessage.###.js(追加箇所のみ)
import wixData from 'wix-data';
import {sendMessage,getMyMessages} from 'backend/Messages'
$w.onReady( function () {
    /* 省略 */
    initData()
    $w('#repeater1').onItemReady( ($item, itemData, index) => {
        $item('#text7').text = itemData.body
        $item('#text6').text = itemData.fromMember.nickname
        $item('#text5').text = itemData.toMember.nickname
    })
    /* 省略 */
});

async function initData(keyword){
    const messages = await getMyMessages()
    console.log(messages)
    $w('#repeater1').data = messages
}

バックエンドコードで新たに定義した関数getMyMessages()を利用するのでインポート文を変更(import {sendMessage,getMyMessages} from 'backend/Messages')。リピータのデータ更新はいくつかのタイミングで実施する可能性があるので関数化する(async function initData(keyword){..})。リピータはデータが更新されると各要素の設定イベントが呼び出されるので定義する($w('#repeater1').onItemReady(($item, itemData, index) => {..}))。
リピータの表示処理は以前の記事で触れた。
関数initData()を呼び出すタイミングはページが表示されたときメッセージを送信したときの2回。

送信ボタンをしたときの処理
    $w('#button1').onClick(async evnet => {         // <-- asyncを付加

        const messageObj = {}
        messageObj.toMember = $w('#dropdown1').value
        messageObj.body = $w('#textBox1').value

        await sendMessage(messageObj)               // <-- awaitを付加
        .then(isSuccess => {
            if(isSuccess){
                $w('#dropdown1').value = null
                $w('#textBox1').value = null
                $w('#text4').text = 'メッセージが送信されました。'
            }else{
                $w('#text4').text = 'メッセージが送信できませんでした。'
            }
        })

        initData()                         // <-- initData()を呼び出す。
    })

送信ボタンを押した時の処理として関数sendMessage()が呼び出される。この処理が完了したときに関数initData()を呼び出す。sendMessage()の呼び出しをawaitを付加し処理の完了を待つ。また、クリックイベントの呼び出しにはasyncを付加しておく。

動作確認

sendMessage画面を開く。

sendMessage画面
sendMessage画面(会員1)

1度目の動作確認で送信した内容が表示された。試しに別の会員(会員2)からもメッセージを送る。

sendMessage画面
sendMessage画面(会員1)

会員2から送信された会員1宛てのメッセージも無事表示された。

う~ん、ダッサ。見づらい。そのうち考えよう。今回は終わり。

https://n5creation.wixstudio.io/zenn-00021

まとめ

「Wix会員エリア」をインストールして会員情報を使ってみた。WixAppを活用すれば、サイトの機能を拡張することが容易。提供元もWixだからなんだと思うけど、会員情報を扱うためのライブラリも当然揃ってる。インストールして、操作すること自体は全く苦労しなかった。ただ、会員情報を扱う3つのコレクション(PrivateMembersData,FullData,PublicData)の違いを見つけるのに苦労した。もちろんコレクションについては説明されているけど、フィールドなど構造の話。それらの意図については触れてないのが悩んだ原因。これらのコレクションは作成する機能の利用場面と利用者等によって使い分ける必要がある。とりあえずPublicDataはプロフィールを公開している会員情報なのでこれを使うかな。メッセージングサービス(モドキ)まで作る必要無かった。多分、ちゃんとしたものがWixAppMarketで提供されてると思う。
会員情報とは別に感動した点がある。wixData.query('Messages')で利用した.include('toMember')。参照先のデータを再度wixData.query()で取得しなければならないかと不安だったが、さすがって感じだった。逆に.or()が少しイマイチ正しいのかよくわからなかった。.or()の引数として与えられるものがWixDataQueryオブジェクト(wixData.query('Messages'))で、単に条件だけを指定するわけじゃなかった。こうあるべきのような、やっぱり違うような、まだちょっと納得し切れてない。

https://ja.wix.com/app-market

つづき

WixStudioでVeloを使う Step 22 「リピータとカスタムCSS」

Discussion