inertia.js(react) + survey.js - 3: survey作成
さて、今、質問セットが定義できたので、これをcreate時に渡してセレクトボックスを作成してみよう
Create時にこの情報を渡す
<?php
namespace App\Http\Controllers;
use App\Models\Survey;
use App\Models\SurveyQuestion;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class SurveyController extends Controller
{
/**
* Show the form for creating a new resource.
*/
public function create(): Response
{
$surveyQuestionSets = SurveyQuestion::pluck('name', 'id');
return Inertia::render('Surveys/Create', [
'surveyQuestionSets' => $surveyQuestionSets,
]);
}
このようにしておく、これでviewからsurveyQuestionSets変数をループしてセレクトボックスを作れるようになるだろう
create view(コンポーネント)
resources/js/Pages/Surveys/Create.jsx
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import {
Head, useForm, usePage,
} from '@inertiajs/react';
import { useLaravelReactI18n } from 'laravel-react-i18n';
import InputError from '@/Components/InputError';
import InputLabel from '@/Components/InputLabel';
import PrimaryButton from '@/Components/PrimaryButton';
import TextInput from '@/Components/TextInput';
export default function SurveyCreate({ auth, surveyQuestionSets }) {
const { t } = useLaravelReactI18n();
const {
data, setData, post, errors, processing, recentlySuccessful,
} = useForm({
title: '',
description: '',
settings: '',
survey_question_set_id: '',
});
const submit = (e) => {
e.preventDefault();
post(route('surveys.store'));
};
return (
<AuthenticatedLayout
user={auth.user}
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">{t('Create New Survey')}</h2>}
>
<Head title={t("Create New Survey")} />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div>
<div>
<InputLabel htmlFor="title" value={t('Title')} />
<TextInput
id="title"
className="mt-1 block w-full"
value={data.title}
onChange={(e) => setData('title', e.target.value)}
onSubmit={submit}
/>
<InputError className="mt-2" message={errors.title} />
</div>
<div className="mt-3">
<InputLabel htmlFor="description" value={t('Description')} />
<TextInput
id="description"
className="mt-1 block w-full"
value={data.description}
onChange={(e) => setData('description', e.target.value)}
onSubmit={submit}
/>
<InputError className="mt-2" message={errors.description} />
</div>
<div className="mt-3">
<InputLabel htmlFor="survey_question_set_id" value={t("Question Set")} />
<select
id="survey_question_set_id"
className="mt-1 block w-full"
value={data.survey_question_set_id}
onChange={(e) => setData('survey_question_set_id', e.target.value)}
>
<option value="">-- {t("Select a Question Set")} --</option>
{Object.entries(surveyQuestionSets).map(([key, value]) => (
<option key={key} value={key}>
{value}
</option>
))}
</select>
<InputError className="mt-2" message={errors.survey_question_set_id} />
</div>
<form onSubmit={submit} className="mt-6 space-y-6">
<div className="flex items-center gap-4">
<PrimaryButton disabled={processing}>{t('Save')}</PrimaryButton>
</div>
</form>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
これでok。翻訳は適当に与えてね! (lang/ja.json)
"Already registered?": "登録済みの方はこちら",
"Are you sure you want to delete your account?": "アカウントを削除しますか?",
"Action": "操作",
+ "Available Surveys": "利用可能なアンケート調査",
"Cancel": "キャンセル",
"Click here to re-send the verification email.": "確認メールの再送はこちら",
"Confirm": "確認",
"Confirm Password": "パスワード(確認用)",
"Current Password": "現在のパスワード",
"Create": "作成",
+ "Create New Survey": "新しいアンケート調査の作成",
"Create New User": "新規ユーザー作成",
"Created At": "作成日時",
"Dashboard": "ダッシュボード",
@@ -55,6 +57,7 @@
"Profile": "プロフィール",
"Profile Information": "プロフィール情報",
"Properties": "プロパティー",
+ "Question Set": "質問セット",
"Regards": "よろしくお願いします",
"Register": "アカウント作成",
"Remember me": "ログイン状態を保持する",
@@ -68,6 +71,7 @@
"Saved.": "保存が完了しました。",
"Server Error": "サーバーエラー",
"Service Unavailable": "サービスは利用できません",
+ "Select a Question Set": "質問セットを選択してください",
"Showing": "表示中",
"Surveys": "アンケート調査",
"Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn't receive the email, we will gladly send you another.": "ご登録ありがとうございます。入力いただいたメールアドレス宛に確認メールを送信しました。メールをご確認いただき、メールに記載されたURLをクリックして登録を完了してください。メールが届いていない場合、メールを再送できます。",
@@ -77,6 +81,7 @@
"This action is unauthorized.": "禁止されているアクションです",
"This is a secure area of the application. Please confirm your password before continuing.": "ここはアプリケーションの安全な領域です。パスワードを入力して続行ください。",
"This password reset link will expire in :count minutes.": "このパスワード再設定リンクの有効期限は:count分です。",
+ "Title": "タイトル",
store
とりあえず
app/Http/Controllers/SurveyController.php
public function store(Request $request)
{
dd($request->all());
}
としてデーターが正しく渡っていればok。渡ったデーターをDBに登録していくんだけど、質問のjsonデーターを展開して自身のDBに再度「エレメント」として取り込んでいく必要があったりする。
質問セットの展開
の前に場所がない
というわけでテーブルを設計していくぞ〜い
これは以下3つのモデルで構成される
SurveyPage
SurveyElement
SurveyElementChoice
これは上記の概念に基いている。Question1本1本はElementという単位で保存しているし、Choiceは多肢選択用でどうしても必要になってくる。
以下のようにモデルを作成。
% ./vendor/bin/sail artisan make:model SurveyPage -m
INFO Model [app/Models/SurveyPage.php] created successfully.
INFO Migration [database/migrations/2023_10_01_193155_create_survey_pages_table.php] created successfully.
% ./vendor/bin/sail artisan make:model SurveyElement -m
INFO Model [app/Models/SurveyElement.php] created successfully.
INFO Migration [database/migrations/2023_10_01_193204_create_survey_elements_table.php] created successfully.
% ./vendor/bin/sail artisan make:model SurveyElementChoice -m
INFO Model [app/Models/SurveyElementChoice.php] created successfully.
INFO Migration [database/migrations/2023_10_01_193208_create_survey_element_choices_table.php] created successfully.
SurveyPage
Schema::create('survey_pages', function (Blueprint $table) {
$table->id();
$table->foreignId('survey_id')->constrained();
$table->string('name')->nullable()->default('');
$table->string('title')->nullable()->default('');
$table->string('description')->default('');
$table->timestamps();
});
モデルも変更しておくよ〜
app/Models/SurveyPage.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class SurveyPage extends Model
{
use HasFactory;
protected $fillable = [
'survey_page_id',
'type',
'name',
'title',
'description',
'is_required',
];
public function elements(): HasMany
{
return $this->hasMany(SurveyElement::class);
}
}
まあ実はnameとかdescriptionとか入れてないんすけどね
SurveyElement
Schema::create('survey_elements', function (Blueprint $table) {
$table->id();
$table->foreignId('survey_page_id')->constrained();
$table->string('title');
$table->boolean('is_required')->default(0);
$table->enum('type', ['text', 'radiogroup', 'checkbox']);
$table->timestamps();
});
モデルも更新
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class SurveyElement extends Model
{
use HasFactory;
protected $fillable = [
'survey_page_id',
'type',
'title',
'is_required',
];
public function choices(): HasMany
{
return $this->hasMany(SurveyElementChoice::class);
}
}
SurveyChoice
Schema::create('survey_element_choices', function (Blueprint $table) {
$table->id();
$table->foreignId('survey_element_id')->constrained();
$table->string('choice');
$table->timestamps();
});
app/Models/SurveyElementChoice.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class SurveyElementChoice extends Model
{
use HasFactory;
protected $fillable = [
'survey_element_id',
'choice',
];
public function SurveyElement(): BelongsTo
{
return $this->hasMany(SurveyElement::class);
}
}
以下はchatgpt作成の関係図
[ surveys ]
|
| 1
| n
[survey_pages] --1:n-- [survey_elements] --1:n-- [survey_element_choices]
再度storeを開発していく
さて、現状のstoreは
public function store(Request $request)
{
dd($request->all());
}
このようにリクエストダンプしているだけであった。
試しに適当に送信すると、以下のようなデーターになる
array:4 [▼ // app/Http/Controllers/SurveyController.php:46
"title" => "てすと"
"description" => "memo"
"settings" => null
"survey_question_set_id" => "1"
]
冒頭でモデルのuseが必要なのでばばっと登録しておこう
<?php
namespace App\Http\Controllers;
use App\Models\Survey;
use App\Models\SurveyQuestion;
use App\Models\SurveyPage;
use App\Models\SurveyElement;
use App\Models\SurveyElementChoice;
survey_question_set_idからsurvey_questionを取得する
ここでsurvey_question_set_id = 1とか取れているので、この情報を元に質問セットを取得する
namespace App\Http\Controllers;
use App\Models\Survey;
use App\Models\SurveyQuestion;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class SurveyController extends Controller
{
public function store(Request $request)
{
$data = $request->all();
$data['settings'] = [];
// TODO: 本来はvalidationをかける
$surveyQuestionSetId = $request->survey_question_set_id;
$surveyStructure = SurveyQuestion::findOrFail($surveyQuestionSetId)->question_data;
dd($surveyStructure);
単純にfindOrFailして当該質問セットのrowを取ってきたのちにquestion_dataを取る。これはjsonが入っていたものだけどarray castにより
このような「php配列」のに変換されていることがdumpで確認できる。これをsurveyに登録するにあたってモデルを更新しなくてはならない。webUIから更新するタイプはfillableが必須である。
app/Models/Survey.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Survey extends Model
{
use HasFactory;
protected $fillable = [
'title',
'description',
];
}
本当はsettings周りも一気にやりたいところだけど、わけがわからなくなってくるかもなのでちょっとずつ、ちょっとずつ。
<?php
namespace App\Http\Controllers;
use App\Models\Survey;
use App\Models\SurveyQuestion;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
use Illuminate\Support\Facades\DB;
class SurveyController extends Controller
public function store(Request $request)
{
$data = $request->all();
$data['settings'] = [];
// TODO: 本来はvalidationをかける
$surveyQuestionSetId = $request->survey_question_set_id;
$surveyStructure = SurveyQuestion::find($surveyQuestionSetId)->question_data;
DB::beginTransaction();
$survey = Survey::create($data);
// DB::commit();
これで、titleとdescriptionは登録されるのだが、問題はここではない。
survey pageの登録
一番外側のpageを登録していく
public function store(Request $request)
{
$data = $request->all();
$data['settings'] = [];
// TODO: 本来はvalidationをかける
$surveyQuestionSetId = $request->survey_question_set_id;
$surveyStructure = SurveyQuestion::find($surveyQuestionSetId)->question_data;
DB::beginTransaction();
$survey = Survey::create($data);
foreach ($surveyStructure['pages'] as $page) {
$data = [
'name' => $page['name'],
'survey_id' => $survey['id'],
];
dump($data);
$surveyPage = SurveyPage::create($data);
}
exit;
この辺は常に
この配列を意識しといてください。要するに
基本情報
職業と収入
の段が登録された。
elementsの登録
elementsはpageの下に位置するよね。だからpageを作成した後じゃないと作成できませんよっと。
DB::beginTransaction();
$survey = Survey::create($data);
foreach ($surveyStructure['pages'] as $page) {
$data = [
'name' => $page['name'],
'survey_id' => $survey['id'],
];
$surveyPage = SurveyPage::create($data);
foreach ($page['elements'] as $element) {
$data = [
'survey_page_id' => $surveyPage->id,
'type' => $element['type'],
'title' => $element['title'],
'is_required' => $element['isRequired'] ?? false,
];
dump($data);
$surveyElement = SurveyElement::create($data);
}
}
exit;
要するに「お名前を…」みたいな奴が登録されていく。以下はdumpである。
choiceの登録
もう一歩!
public function store(Request $request)
{
$data = $request->all();
$data['settings'] = [];
// TODO: 本来はvalidationをかける
$surveyQuestionSetId = $request->survey_question_set_id;
$surveyStructure = SurveyQuestion::find($surveyQuestionSetId)->question_data;
DB::beginTransaction();
$survey = Survey::create($data);
foreach ($surveyStructure['pages'] as $page) {
$data = [
'name' => $page['name'],
'survey_id' => $survey['id'],
];
$surveyPage = SurveyPage::create($data);
foreach ($page['elements'] as $element) {
$data = [
'survey_page_id' => $surveyPage->id,
'type' => $element['type'],
'title' => $element['title'],
'is_required' => $element['isRequired'] ?? false,
];
$surveyElement = SurveyElement::create($data);
if ($choices = $element['choices'] ?? null) {
foreach ($choices as $choice) {
$data = [
'survey_element_id' => $surveyElement->id,
'choice' => $choice,
];
dump($data);
SurveyElementChoice::create($data);
}
}
}
}
exit;
DB::commit();
return redirect(route('surveys.index'))
->with(['success' => __('New Survey Created')])
;
}
これはradioとかcheckboxで使われる枝ね。
保存
ここまでよさそうであればcommitして実際に保存してゆく。
最終のコード
<?php
namespace App\Http\Controllers;
use App\Models\Survey;
use App\Models\SurveyQuestion;
use App\Models\SurveyPage;
use App\Models\SurveyElement;
use App\Models\SurveyElementChoice;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class SurveyController extends Controller
public function store(Request $request): RedirectResponse
{
$data = $request->all();
$data['settings'] = [];
// TODO: 本来はvalidationをかける
$surveyQuestionSetId = $request->survey_question_set_id;
$surveyStructure = SurveyQuestion::find($surveyQuestionSetId)->question_data;
DB::beginTransaction();
$survey = Survey::create($data);
foreach ($surveyStructure['pages'] as $page) {
$data = [
'name' => $page['name'],
'survey_id' => $survey['id'],
];
$surveyPage = SurveyPage::create($data);
foreach ($page['elements'] as $element) {
$data = [
'survey_page_id' => $surveyPage->id,
'type' => $element['type'],
'title' => $element['title'],
'is_required' => $element['isRequired'] ?? false,
];
$surveyElement = SurveyElement::create($data);
if ($choices = $element['choices'] ?? null) {
foreach ($choices as $choice) {
$data = [
'survey_element_id' => $surveyElement->id,
'choice' => $choice,
];
SurveyElementChoice::create($data);
}
}
}
}
DB::commit();
return redirect(route('surveys.index'))
->with(['success' => __('New Survey Created')])
;
}
作成されたsurveyを表示
つわけで作成されて何も出てこないのもアレなので取り出しておく。
resources/js/Pages/Surveys/Index.jsx
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head } from '@inertiajs/react';
import PrimaryButton from '@/Components/PrimaryButton';
import SecondaryButton from '@/Components/SecondaryButton';
import { useLaravelReactI18n } from 'laravel-react-i18n';
import { useConfiguredDayjs } from '@/hooks/useConfiguredDayjs';
import {
VscSettingsGear,
VscOpenPreview,
} from 'react-icons/vsc';
export default function SurveyIndex({ auth, surveys }) {
const { t } = useLaravelReactI18n();
return (
<AuthenticatedLayout
user={auth.user}
header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">{t('Surveys')}</h2>}
>
<Head title="Survey.js" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white p-6 rounded shadow-md max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-4">
<h3 className="text-2xl font-semibold">
{t('Available Surveys')}
</h3>
<PrimaryButton href={route('surveys.create')}>
{t('Create New Survey')}
</PrimaryButton>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
{surveys.map((survey, index) => (
<div key={index} className="bg-white rounded-lg shadow-lg p-4 border border-gray-300">
<div className="flex justify-between items-center mb-2">
<h4 className="text-lg font-semibold">
{survey.title}
</h4>
<SecondaryButton href={route('surveys.edit', survey.id)}>
<VscSettingsGear className="mr-2" />
{' '}
{t('Settings')}
</SecondaryButton>
</div>
<p className="text-sm text-gray-700 mb-3">
{survey.description}
</p>
<PrimaryButton href={route('surveys.show', survey.id)}>
<VscOpenPreview className="mr-2" />
{' '}
{t('Preview')}
</PrimaryButton>
</div>
))}
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
);
}
ちょい洗練させました