SpringBootでWebアプリ開発(年間一覧表示・カテゴリ連動・リダイレクト)

SpringBoot Webアプリ開発チャレンジ(4~7日目)
の続き。


年間一覧の作成

  • ページの見出しに年間の収支合計を表示
  • 月別の合計を表形式で表示
  • 「前の年」「次の年」のリンクを表示

完成イメージはこちら(データがほぼ入ってないけど…)

IncomeOutgoService.java

  • result = new Object[12][36];で配列のスペースを確保しておかないとnullPointerexceptionエラーになった
  • 当初result = new Object[12][];と宣言しており、result[i][0] = outgoとしても値が入らなかった
  • [i][0]のスペースが確保されていなかったから
  • データがないときnullが入るのでそのときは合計を0にする(付け刃的…)
// yyyy年MM月の文字列・月間の収入合計・月間の支出合計を二次元配列で取得
public Object[][] monthTotal(Date today){
    Calendar startCal = createFirstYear(today);
    Object result[][];
    result = new Object[12][36];

    for (int i = 0; i <= 11; i++){
        Date startDate = startCal.getTime();
        // yyyy-MMの文字列を格納
        SimpleDateFormat thisMonthFormat = new SimpleDateFormat("yyyy年MM月");
        String dateIndex = thisMonthFormat.format(startDate);
        result[i][0] = dateIndex;

        startCal.add(Calendar.MONTH, 1);
        Date lastDate = startCal.getTime();

        // 収入合計を配列に格納
        Integer income = incomeOutgoRepository.costTotal("income", startDate, lastDate);
        if (income == null){
            result[i][1] = 0;
        }else{
            result[i][1] = income;
        }

        // 支出合計を配列に格納
        Integer outgo = incomeOutgoRepository.costTotal("outgo", startDate, lastDate);
        if (outgo == null){
            result[i][2] = 0;
        }else{
            result[i][2] = outgo;
        }

    }
    return result;
}

IncomeOutgoController.java

// 月ごとの合計
@GetMapping("year/{thisYearPath}")
 public String year(@PathVariable String thisYearPath, Model model){

    model.addAttribute("monthTotal", incomeOutgoService.monthTotal(centerYear(thisYearPath)));

}

収支のデータがないとき、年間の収支合計を例外処理で0にする

//年間合計
try{
    Integer yearTotal = yearIncomeTotal - yearOutgoTotal;
    model.addAttribute("yearTotal", yearTotal);

}catch(NullPointerException e){
    model.addAttribute("yearTotal", 0);
}

year.html

  • 表の左側から月(yyyy-MM)→収入合計→支出合計→収支合計→月別一覧のリンクの順に表示
<tbody>
    <tr th:each="cost:${monthTotal}">
        <th th:text="${cost[0]}"></th>
        <td th:text="${cost[1]}"></td>
        <td th:text="${cost[2]}"></td>
        <td th:text="${cost[1]-cost[2]}"></td>
        <td>
            <a class="btn btn-outline-secondary" th:href="@{/income_outgo/month/{monthPath}(monthPath=${cost[0]})}">一覧</a>
        </td>
    </tr>
</tbody>

収支編集後のリダイレクト先

  • 同じ画面にリダイレクトするように、return "redirect:/income_outgo/edit"を`return "redirect:/income_outgo/{id}/edit"に変更
@PutMapping("{id}")
public String update(@PathVariable Long id, @ModelAttribute IncomeOutgo incomeOutgo){
    incomeOutgo.setId(id);
    incomeOutgoService.save(incomeOutgo);
    return "redirect:/income_outgo/{id}/edit";
}

データ削除後のリダイレクト先

なぜか3日くらい解決できず...

NGパターン①

IncomeOutgoController.java

@DeleteMapping("{id}")
public String destroy(@PathVariable  Long id){
    Date deleteDate = incomeOutgoService.findById(id).getDate();
    SimpleDateFormat thisMonthPathFormat = new SimpleDateFormat("yyyy-MM");
    String deleteDatePath = thisMonthPathFormat.format(deleteDate);
    incomeOutgoService.deleteById(id);
    return "redirect:income_outgo/month/{deleteDatePath}";
}

income_outgo/month/yyyy-MMのパスにリダイレクトするように、無理やりdeleteDatePathを作っていた。
もちろんうまくいくはずがなく、『こんなパス、テンプレートにないですよ』と怒られる...

org.thymeleaf.exceptions.TemplateInputException: Error resolving template "income_outgo/month/{deleteDatePath}", template might not exist or might not be accessible by any of the configured Template Resolvers

NGパターン②
* View側で、hiddenを用いてincome_outgo/month/2018-12のパスを生成して送ってみる

year.html

<form th:action="@{/income_outgo/{id}/(id=*{id})}" th:method="delete">
    <input type="hidden" th:path="@{/income_outgo/month/{thisMonthPath}(thisMonthPath=${thisMonthPath})}"></inputhidden>
    <input class="btn btn-outline-secondary" type="submit" value="削除"/>
</form>
  • →そもそもGETメソッドではないためエラーになる(そりゃそうだ)
 : Resolved exception caused by handler execution: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported

OKパターン

  • リダイレクト先が/income_outgo/month/YYYY-mmになるようにControllerとViewを変更する
    • Controller側の@DeleteMappingで設定するパスと、View側のth:actionで設定するパスを合わせる
  • 同じ画面にリダイレクトするように、月別一覧のGetMappingブロックにcenterMonthPathを追加

IncomeOutgoController.java

@GetMapping("month/{thisMonthPath}")
public String month(@PathVariable String thisMonthPath, Model model) throws ParseException {

    // 基準の月のパス取得
    String centerMonthPath = thisMonthPathFormat.format(centerMonth(thisMonthPath));
    model.addAttribute("centerMonthPath", centerMonthPath);

}

@DeleteMapping("month/{centerMonthPath}/{id}")
public String destroy(@PathVariable  Long id, @PathVariable String centerMonthPath){
    incomeOutgoService.deleteById(id);
    return "redirect:/income_outgo/month/{centerMonthPath}";
}

year.html

<form th:action="@{/income_outgo/month/{thisMonthPath}/{id}/(thisMonthPath=${thisMonthPath},id=*{id})}" th:method="delete">
    <input class="btn btn-outline-secondary" type="submit" value="削除"/>
</form>

centerMonthPaththisMonthPathの使い分けとしては、2018年12月に2018年11月の一覧画面にアクセスしているとすると
* centerMonthPath2018-11
* thisMonthPath2018-12

としている。
ネーミングいまいち...

カテゴリの連動

カテゴリ編集画面

完成イメージはこちら

  • 収入と支出でカテゴリが分かれているのが少々ネック
  • th:switchでtypeのincome, outgoで場合分けする
    • もともと登録していた方にデフォルトでチェックが入るように
<div th:switch="*{type}">
    <div th:case="outgo">
        <div class="col-sm-2">
            <input type="radio" class="form-check-input" name="type" value="outgo" checked="checked">
            <label class="form-check-label">支出</label>
        </div>
        <div class="col-sm-2">
            <input type="radio" class="form-check-input" name="type" value="income">
            <label class="form-check-label">収入</label>
        </div>
    </div>
    <div th:case="income">
        <div class="col-sm-2">
            <input type="radio" class="form-check-input" name="type" value="outgo">
            <label class="form-check-label">支出</label>
        </div>
        <div class="col-sm-2">
            <input type="radio" class="form-check-input" name="type" value="income" checked="checked">
            <label class="form-check-label">収入</label>
        </div>
    </div>
</div>

収支の編集画面

完成イメージはこちら

  • 条件演算子で、もともと選択していたものがデフォルトで入るようにする
    • ${incomeOutgo.category_id} == *{id})? 'selected'" この部分
<label class="col-sm-2 col-form-label">カテゴリ</label>
<div class="col-sm-6">
    <select class="custom-select" name="category_id">
        <option disabled>--支出--</option>
        <div th:each="category:${categories_outgo}" th:object="${category}">
            <option th:value="*{id}" th:text="*{name}" th:selected="(${incomeOutgo.category_id} == *{id})? 'selected'"></option>
        </div>
        <option disabled>--収入--</option>
        <div th:each="category:${categories_income}" th:object="${category}">
            <option th:value="*{id}" th:text="*{name}" th:selected="(${incomeOutgo.category_id} == *{id})? 'selected'"></option>
        </div>
    </select>
</div>

ここまでで実装されたソースコードはこちら
https://github.com/SunHigh105/KakaiboApp/commit/b17100805ce78e3fe8c3e4d9f493901359520587

SNSでもご購読できます。

コメントを残す

*