見出し画像

inertia.js(react) + survey.js - 6: 設定の詳細変更(json連携)

さてさてさて、今surveyの構造を今一度見てみよう。

        Schema::create('surveys', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description')->nullable();
            $table->json('settings')->nullable();
            $table->timestamps();
        });

このsettingsで設定を変更できるようになっているんだけど、未使用である。これはsurvey.jsの設定が投入される事を想定している。たとえば現在

Nextが示すように2ページでの表示になっている。これはPage構造をそのようにしているからなんだけど、1ページに全部表示したいという事も可能なわけだ。questionsOnPageMode という設定に見ることができる

まずは手動で確認してみる

今previewで表示しているので、ここに無理矢理ハードコードしてつっこんでみよう。

    public function show(Request $request, Survey $survey, SurveyService $surveyService): Response
    {
        $pagesData = $surveyService->getSurveyData($survey);
        $surveyData = [
            'questionsOnPageMode' => 'singlePage',
            'pages' => $pagesData,
        ];
        $surveyData = json_encode($surveyData, JSON_UNESCAPED_UNICODE);

        $responseCount = 0;
        if ($previewData = $request->session()->get('preview_data')) {
            $responseCount = count($previewData);
        }

        return Inertia::render('Surveys/Show', [
            'surveyModel'   => $survey,
            'surveyData'    => $surveyData,
            'responseData'  => $previewData,
            'readOnly'      => $responseCount ? true : false,
        ]);
    }


1ページになった!

このように、page配列と同列に設定を与えるとそれを食ってくれる。なお、

  • "singlePage" - Combines all survey pages into a single page.

  • "questionPerPage" - Creates a separate page for every question.

  • "standard" (default) - Retains the original structure specified in the JSON schema.

ってことなんだけど、変な値を食わせたらdefaultに落ちるみたいだからそこは気にしなくてもいいっぽい。

設定インタフェース

とりあえずEditだけベタっと付けてみよう

export default function SurveyEdit({ auth, survey }) {
  const { t } = useLaravelReactI18n();

  const {
    data, setData, put, errors, processing, recentlySuccessful,
  } = useForm({
    title: survey.title,
    description: survey.description,
    settings: '',
  });

  const updateSetting = (key, value) => {
    setData('settings', {
      ...data.settings,
      [key]: value,
    });
  };

...
              <div className="mt-3">
                <InputLabel htmlFor="questionsOnPageMode" value="questionsOnPageMode" />

                <select
                  id="questionsOnPageMode"
                  value={data.settings?.questionsOnPageMode || 'standard'}
                  onChange={(e) => updateSetting('questionsOnPageMode', e.target.value)}
                >
                  <option value="singlePage">singlePage</option>
                  <option value="questionPerPage">questionPerPage</option>
                  <option value="standard">standard</option>
                </select>
              </div>

こんな感じで付けると

こんな感じになるから、これを保存する

    public function update(Request $request, Survey $survey): RedirectResponse
    {
        $data = $request->all();
        $survey->update($data);
        return redirect(route('surveys.index', $survey))
            ->with(['success' => __('Survey Settings Updated')]);

    }
class Survey extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'description',
        'settings',
    ];
> Survey::find(2)
[!] Aliasing 'Survey' to 'App\Models\Survey' for this Tinker session.
= App\Models\Survey {#7496
    id: 2,
    title: "あああ",
    description: "あ",
    settings: "{"questionsOnPageMode": "questionPerPage"}",
    created_at: "2023-10-02 12:38:58",
    updated_at: "2023-10-02 13:22:14",
  }

ハードコードしてたところを直す

    public function show(Request $request, Survey $survey, SurveyService $surveyService): Response
    {
        $pagesData = $surveyService->getSurveyData($survey);
        $settings = $survey->settings;
        $surveyData = [
            'pages' => $pagesData,
        ];
        if ($settings) {
            $surveyData = array_merge($surveyData, $settings);
        }

        $surveyData = json_encode($surveyData, JSON_UNESCAPED_UNICODE);

        $responseCount = 0;
        if ($previewData = $request->session()->get('preview_data')) {
            $responseCount = count($previewData);
        }

        return Inertia::render('Surveys/Show', [
            'surveyModel'   => $survey,
            'surveyData'    => $surveyData,
            'responseData'  => $previewData,
            'readOnly'      => $responseCount ? true : false,
        ]);
    }

さらには編集モードで設定があるときはそれをデフォルトにする

  const {
    data, setData, put, errors, processing, recentlySuccessful,
  } = useForm({
    title: survey.title,
    description: survey.description,
    settings: survey.settings,
  });

これで、とりあえずは完了ではある。

リファクタリングの検討

このままの勢いでインタフェースを増やしたりするとコードがどんどん増えてしまってよろしくない

              <div className="mt-3">
                <InputLabel htmlFor="questionsOnPageMode" value="questionsOnPageMode" />

                <select
                  id="questionsOnPageMode"
                  value={data.settings?.questionsOnPageMode || 'standard'}
                  onChange={(e) => updateSetting('questionsOnPageMode', e.target.value)}
                >
                  <option value="singlePage">singlePage</option>
                  <option value="questionPerPage">questionPerPage</option>
                  <option value="standard">standard</option>
                </select>
              </div>

ハードコードしてるのはこの部分くらいなので、これを何とかしてみよう

コンポーネント化

              <div className="mt-3">
                <SurveySelectSetting
                  settingKey="questionsOnPageMode"
                  value={data.settings?.questionsOnPageMode}
                  onChange={updateSetting}
                  options={["singlePage", "questionPerPage", "standard"]}
                />
              </div>

みたいにしておいて
resources/js/Components/SurveySelectSetting.jsx

import InputLabel from '@/Components/InputLabel';
export default function SurveySelectSetting({ settingKey, value, onChange, options }) {
  return (
    <>
      <InputLabel htmlFor={settingKey} value={settingKey} />
      <select
        id={settingKey}
        value={value || 'standard'}
        onChange={(e) => onChange(settingKey, e.target.value)}
      >
        {options.map((option) => (
          <option key={option} value={option}>
            {option}
          </option>
        ))}
      </select>
    </>
  );
}

を作る。これで一応の解決は見られる

controllerからデーターの注入

このviewのハードコードをさらに抽象化する

    public function edit(Survey $survey, SurveyService $surveyService): Response
    {
        $surveyMetaData = $surveyService->getSettingsMetadata();
        return Inertia::render('Surveys/Edit', [
            'survey'         => $survey,
            'surveyMetaData' => $surveyMetaData,
        ]);
    }

このようにメタデーターを受信できるようにする。メタデーターの構造体自体はサービスに定義しといたらいいだろう。

app/Services/SurveyService.php

    public function getSettingsMetadata(): array
    {
        return [
            'questionsOnPageMode' => [
                'type' => 'select',
                'options' => ['singlePage', 'questionPerPage', 'standard'],
                'default' => 'standard',
                'label' => __('Question On Page Mode'),
            ],
        ];
    }

あとはコンポーネントをちょっと変更して

resources/js/Components/SurveySelectSetting.jsx

import InputLabel from '@/Components/InputLabel';

export default function SurveySelectSetting({ settingKey, value, onChange, options, label }) {
  return (
    <>
      <InputLabel htmlFor={settingKey} value={label || settingKey} />
      <select
        id={settingKey}
        value={value || 'standard'}
        onChange={(e) => onChange(settingKey, e.target.value)}
      >
        {options.map((option) => (
          <option key={option} value={option}>
            {option}
          </option>
        ))}
      </select>
    </>
  );
}

Edit.jsxも以下のように変更する

              {Object.entries(surveyMetaData).map(([settingKey, metaData]) => {
                const { type, options, default: defaultValue, label } = metaData;
                switch (type) {
                  case 'select':
                    return (
                      <div className="mt-3" key={settingKey}>
                        <SurveySelectSetting
                        settingKey={settingKey}
                        value={data.settings?.[settingKey] || defaultValue}
                        onChange={updateSetting}
                        options={options}
                        label={label}
                        />
                      </div>
                    );
                  default:
                    return null;
                }
              })}

設定の追加

ここまでのプログラミングがよくわかんくてもメンテナンスはここから行うので大丈夫っちゃ大丈夫。

まず設定したい項目を探して必ずapiドキュメントを引く。ここではshowQuestionNumbers の設定を追加してみよう


    public function getSettingsMetadata(): array
    {
        return [
            'questionsOnPageMode' => [
                'type' => 'select',
                'options' => ['singlePage', 'questionPerPage', 'standard'],
                'default' => 'standard',
                'label' => __('Question On Page Mode'),
            ],
            'showQuestionNumbers' => [
                'type' => 'select',
                'options' => ['on', 'onpage', 'off'],
                'default' => 'on',
                'label' => __('Show Question Numbers'),
            ],
        ];
    }

このように増やすと…

こうなる。あとは正しく動作する事を確認すればよい


offにした状態


TODO的なやつ

Create.jsxとEdit.jsxは共通化できる
form request
設定項目の多言語化
プレビューのデーターストアがバグっている

次回

管理機能は一通り終わったのでユーザーがsurveyを受ける機能を作っていこう。これはユーザーがログインした後のdashboardにとりあえず表示すればokであろう

いいなと思ったら応援しよう!