【EC-CUBE 4】管理画面 / 商品管理のカスタマイズ(3) ~ 編集・削除 ~

これまでの記事で、管理画面の商品登録に新メニュー【メーカー管理】を追加し、そこから新しくメーカーを追加できるようにカスタマイズしました。

今回はメーカーの新規登録に加え、保存済メーカーを一覧から選んで編集したり、削除したりする方法を紹介します。

カスタマイズ(前々回)
EC-CUBEの管理画面の商品管理に新メニューを登録した状態の画面
メニューの追加まで実装
カスタマイズ(前回)
メーカー管理メニューの新規登録画面
「dtb_maker」に保存されているメーカーを一覧表示。
また、新しいメーカーを追加するためのフォームを設置。
メーカー管理メニューの保存完了画面
フォームを入力して「新規作成」をクリックすると、新しいメーカーが登録されて一覧も更新される。
カスタマイズ(今回)
編集ボタンと削除ボタンを実装したときの画面
一覧の右端に編集ボタンと削除ボタンを設置。
編集ボタンをクリックしたときの画面
編集をクリックすると、メーカー名とコードを変更するフォームが表示される。
削除ボタンをクリックしたときの画面
削除をクリックすると、削除のためのモーダルウィンドウが表示される。

【動作環境】
EC CUBEのバージョン:4.2.2
サーバー:Xserver

開発前にデバッグモードの設定をお薦めします

デバッグモードを設定しておくと、エラーが起きたときに詳細情報が表示されるようになります。
エラー箇所を探しやすくなるので、開発前に設定しておくのをオススメします。
デバッグモードの設定方法については 以下記事 で解説しています。

カスタマイズ後は、デバッグモードの解除を忘れないように。

目次

カスタマイズの流れ

  • Repositoryの修正(削除メソッドの追加)
  • Controllerの修正(修正&削除のためのコードを追加)
  • Twigテンプレートの修正(修正&削除ボタン、修正フォーム、削除モーダルの追加)
STEP

Repositoryに削除メソッドを追加する

保存済のメーカー情報を削除するため、前回作成したMakerRepositorydelete()メソッドを定義します。

今回は、デフォルトで用意されているClassNameRepository(src/Eccube/Repository)をベースに修正しました。
修正したファイルは「app/Customize/Repository」にアップします。

<?php

/*
 * This file is part of EC-CUBE
 *
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
 *
 * http://www.ec-cube.co.jp/
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Customize\Repository;

use Doctrine\Persistence\ManagerRegistry as RegistryInterface;
use Customize\Entity\Maker;
use Eccube\Repository\AbstractRepository;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;

/**
 * MakerRepository
 */
class MakerRepository extends AbstractRepository
{
    /**
     * MakerRepository constructor.
     *
     * @param RegistryInterface $registry
     */
    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, Maker::class);
    }

    /**
     * メーカーを保存する.
     *
     * @param Maker $maker
     */
    public function save($maker)
    {
        $em = $this->getEntityManager();
        $em->persist($maker);
        $em->flush();
    }

    /**
     * メーカーを削除する.
     *
     * @param Maker $maker
     * 
     * @throws ForeignKeyConstraintViolationException 外部キー制約違反の場合
     * @throws DriverException SQLiteの場合, 外部キー制約違反が発生すると, DriverExceptionをthrowします.
     */
    public function delete($maker)
    {
        $em = $this->getEntityManager();
        $em->remove($maker);
        $em->flush();
    }
}

ちなみに、保存済メーカー情報の編集にはsave()メソッドを利用できるので、特別なメソッドは不要です。

STEP

Controllerを修正する

次に、前回作成したMakerControllerを修正します。

こちらも、デフォルトで用意されているClassNameController(src/Eccube/Controller/Admin/Product)をベースに修正しました。

  • 編集用フォームと編集処理の追加
    • 保存済メーカーの数だけフォームと処理が必要なため、foreachを使って繰り返し処理を行っています。
  • 削除用のdelete()メソッドを追加
    • trycatch を使って、削除が失敗した場合にはエラーメッセージが表示されるように実装しています。

修正したファイルは「app/Customize/Controller/Admin/Product」にアップします。

<?php

/*
 * This file is part of EC-CUBE
 *
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
 *
 * http://www.ec-cube.co.jp/
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Customize\Controller\Admin\Product;

use Eccube\Controller\AbstractController;
use Customize\Entity\Maker;
use Customize\Form\Type\Admin\MakerType;
use Customize\Repository\MakerRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;

class MakerController extends AbstractController
{
    /**
     * @var MakerRepository
     */
    protected $makerRepository;

    /**
     * MakerController constructor.
     *
     * @param MakerRepository $makerRepository
     */
    public function __construct(MakerRepository $makerRepository)
    {
        $this->makerRepository = $makerRepository;
    }

    /**
     * @Route("/%eccube_admin_route%/product/maker", name="admin_product_maker")
     * @Template("@admin/Product/maker.twig")
     */
    public function index(Request $request)
    {
        $makers = $this->makerRepository->findAll();

        $newMaker = new Maker();

        $builder = $this->formFactory
            ->createBuilder(MakerType::class, $newMaker);

        $form = $builder->getForm();

        /**
         * 編集用フォーム
         */
        $forms = [];
        foreach ($makers as $maker) {
            $id = $maker->getId();
            $forms[$id] = $this->formFactory->createNamed('maker_'.$id, MakerType::class, $maker);
        }

        /**
         * 新規登録処理
         */
        if ($request->getMethod() === 'POST') {
            $form->handleRequest($request);
            if ($form->isSubmitted() && $form->isValid()) {
                $this->makerRepository->save($newMaker);
                $this->addSuccess('admin.common.save_complete', 'admin');
                return $this->redirectToRoute('admin_product_maker');
            }
        }

        /*
         * 編集処理
         */
        foreach ($forms as $editForm) {
            $editForm->handleRequest($request);
            if ($editForm->isSubmitted() && $editForm->isValid()) {
                $this->makerRepository->save($editForm->getData());
                $this->addSuccess('admin.common.save_complete', 'admin');
                return $this->redirectToRoute('admin_product_maker');
            }
        }

        $formViews = [];
        foreach ($forms as $key => $value) {
            $formViews[$key] = $value->createView();
        }

        return [
            'form' => $form->createView(),
            'makers' => $makers,
            'newMaker' => $newMaker,
            'forms' => $formViews,
        ];
    }

    /**
     * @Route("/%eccube_admin_route%/product/maker/{id}/delete", requirements={"id" = "\d+"}, name="admin_product_maker_delete", methods={"DELETE"})
     */
    public function delete(Request $request, Maker $maker)
    {
        $this->isTokenValid();

        try {
            $this->makerRepository->delete($maker);
            $this->addSuccess('admin.common.delete_complete', 'admin');
        } catch (\Exception $e) {
            $message = trans('admin.common.delete_error_foreign_key', ['%name%' => $maker->getName()]);
            $this->addError($message, 'admin');
        }

        return $this->redirectToRoute('admin_product_maker');
    }
}
STEP

Twigテンプレートを修正する

最後に、前回作成したTwigテンプレートを修正します。
これも、デフォルトで用意されているclass_name.twig(src/Eccube/Resource/template/admin/Product)をベースに修正しました。

  • 編集および削除ボタン(アイコン)を設置します。
  • 削除モーダルを設置します。
  • 編集および削除ボタン(アイコン)や、決定/キャンセルボタンをクリックしたときの処理を、JavaScript(JQuery)で記述します。

修正したファイルは「app/template/admin/Product」にアップします。

{#
This file is part of EC-CUBE

Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.

http://www.ec-cube.co.jp/

For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends '@admin/default_frame.twig' %}

{% set menus = ['product', 'maker'] %}

{% block title %}{{ 'admin.product.maker_management'|trans }}{% endblock %}
{% block sub_title %}{{ 'admin.product.product_management'|trans }}{% endblock %}

{% form_theme form '@admin/Form/bootstrap_4_horizontal_layout.html.twig' %}
{% block stylesheet %}
    <style type="text/css">
        .list-group-item:hover {
            z-index: inherit;
        }
    </style>
{% endblock stylesheet %}

{% block javascript %}
    <script>
        $(function() {
            // 編集
            $('.sortable-item').on('click', 'a.action-edit', function(e) {
                e.preventDefault();
                var current = $(this).parents('li');
                current.find('.mode-view').addClass('d-none');
                current.find('.mode-edit').removeClass('d-none');
            });
            // 編集キャンセル
            $('.sortable-item').on('click', 'button.action-edit-cancel', function(e) {
                location.href = '{{ url('admin_product_maker') }}';
            });

            // 編集時, エラーがあれば入力欄を表示.
            $('.sortable-item').find('.is-invalid').each(function(e) {
                var current = $(this).parents('li');
                current.find('.mode-view').addClass('d-none');
                current.find('.mode-edit').removeClass('d-none');
            });

            // 削除モーダルのhrefとmessageの変更
            $('#DeleteModal').on('shown.bs.modal', function(event) {
                var target = $(event.relatedTarget);
                // hrefの変更
                $(this).find('[data-method="delete"]').attr('href', target.data('url'));

                // messageの変更
                $(this).find('p.modal-message').text(target.data('message'));
            });
        });
    </script>
{% endblock %}

{% block main %}
    <div class="c-contentsArea__cols">
        <div class="c-contentsArea__primaryCol">
            <div class="c-primaryCol">
                <div class="card rounded border-0 mb-4">
                    <div class="card-body p-0">
                        <div class="card rounded border-0">
                            <ul class="list-group list-group-flush sortable-container">
                                <li class="list-group-item">
                                    <form role="form" class="row" name="form1" id="form1" method="post" action="{{ url('admin_product_maker') }}">
                                        <div class="col-auto align-self-center"><span>{{ 'admin.product.maker.name'|trans }}</span></div>
                                        <div class="col-3 me-2">
                                            {{ form_widget(form._token) }}
                                            {{ form_widget(form.name) }}
                                            {{ form_errors(form.name) }}
                                        </div>
                                        <div class="col-auto align-self-center" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ 'tooltip.product.backend_name'|trans }}">
                                            <span>{{ 'admin.product.maker.code'|trans }}</span>
                                        </div>
                                        <div class="col-3">
                                            {{ form_widget(form.code) }}
                                            {{ form_errors(form.code) }}
                                        </div>
                                        <div class="col-auto">
                                            <button class="btn btn-ec-regular" type="submit">{{ 'admin.common.create__new'|trans }}</button>
                                        </div>
                                    </form>
                                </li>
                                <li class="list-group-item">
                                    <div class="row">
                                        <div class="col-auto"><strong> </strong></div>
                                        <div class="col-auto"><strong>{{ 'admin.common.id'|trans }}</strong></div>
                                        <div class="col-1"><strong>{{ 'admin.product.maker_management'|trans }}</strong></div>
                                    </div>
                                </li>
                                {% for maker in makers %}
                                    <li id="ex-class_name-{{ maker.id }}" class="list-group-item sortable-item" data-class-name-id="{{ maker.id }}">
                                        <div class="row justify-content-around mode-view">
                                            <div class="col-auto d-flex align-items-center"><i class="fa fa-bars text-ec-gray"></i></div>
                                            <div class="col-auto d-flex align-items-center">{{ maker.id }}</div>
                                            <div class="col d-flex align-items-center">{{ maker.name }}</div>
                                            {# 編集アイコンと削除アイコンを設置 #}
                                            <div class="col-auto text-end">
                                                <a class="btn btn-ec-actionIcon me-2 action-edit" data-bs-toggle="tooltip" data-bs-placement="top" title="{{ 'admin.common.edit'|trans }}">
                                                    <i class="fa fa-pencil fa-lg text-secondary"></i>
                                                </a>
                                                <div class="d-inline-block me-2" data-bs-toggle="tooltip"
                                                     data-bs-placement="top" title="{{ 'admin.common.delete'|trans }}">
                                                    <a class="btn btn-ec-actionIcon" data-bs-toggle="modal" data-bs-target="#DeleteModal" data-url="{{ url('admin_product_maker_delete', {'id' : maker.id}) }}" data-message="{{ 'admin.common.delete_modal__message'|trans({ "%name%" : maker.name }) }}">
                                                        <i class="fa fa-close fa-lg text-secondary"></i>
                                                    </a>
                                                </div>
                                            </div>
                                        </div>
                                        {# 編集用のフォームを設置 #}
                                        <form class="row d-none mode-edit" method="post" action="{{ url('admin_product_maker') }}">
                                            {{ form_widget(forms[maker.id]._token) }}
                                            <div class="col-auto align-self-center"><span>{{ 'admin.product.maker.name'|trans }}</span></div>
                                            <div class="col-auto align-items-center">
                                                {{ form_widget(forms[maker.id].name, {'attr': {'data-origin-value': forms[maker.id].name.vars.value}}) }}
                                                {{ form_errors(forms[maker.id].name) }}
                                            </div>
                                            <div class="col-auto align-self-center"><span>{{ 'admin.product.maker.code'|trans }}</span></div>
                                            <div class="col-auto align-items-center">
                                                {{ form_widget(forms[maker.id].code, {'attr': {'data-origin-value': forms[maker.id].code.vars.value}}) }}
                                                {{ form_errors(forms[maker.id].code) }}
                                            </div>
                                            <div class="col-auto align-items-center">
                                                <button class="btn btn-ec-conversion" type="submit">{{ 'admin.common.decision'|trans }}</button>
                                            </div>
                                            <div class="col-auto align-items-center">
                                                <button class="btn btn-ec-sub action-edit-cancel" type="button">{{ 'admin.common.cancel'|trans }}</button>
                                            </div>
                                        </form>
                                    </li>
                                {% endfor %}
                            </ul>
                            <!-- 削除モーダル -->
                            <div class="modal fade" id="DeleteModal" tabindex="-1" role="dialog"
                                 aria-labelledby="DeleteModal" aria-hidden="true">
                                <div class="modal-dialog" role="document">
                                    <div class="modal-content">
                                        <div class="modal-header">
                                            <h5 class="modal-title fw-bold">
                                                {{ 'admin.common.delete_modal__title'|trans }}
                                            </h5>
                                            <button class="btn-close" type="button" data-bs-dismiss="modal" aria-label="Close">

                                            </button>
                                        </div>
                                        <div class="modal-body text-start">
                                            <p class="text-start modal-message"><!-- jsでメッセージを挿入 --></p>
                                        </div>
                                        <div class="modal-footer">
                                            <button class="btn btn-ec-sub" type="button" data-bs-dismiss="modal">
                                                {{ 'admin.common.cancel'|trans }}
                                            </button>
                                            <a class="btn btn-ec-delete" href="#" {{ csrf_token_for_anchor() }}
                                               data-method="delete" data-confirm="false">
                                                {{ 'admin.common.delete'|trans }}
                                            </a>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endblock %}

複製元の「class_name.twig」から、ソート機能をカットした構造になっています。

STEP

キャッシュを削除

ここまでで準備完了です。
管理画面からキャッシュを削除して、商品管理メニューのメーカー管理を確認しましょう。

キャッシュの削除方法
管理画面のキャッシュ管理からキャッシュを削除できます。

【メーカー管理】の右端に、「編集」「削除」ができるアイコンが表示され、保存済メーカーの編集・削除ができるようになりました。

編集ボタンと削除ボタンを実装したときの画面
【メーカー管理】メニューに、編集用の鉛筆アイコンと、削除用の×アイコンが設置されています。
編集ボタンをクリックしたときの画面
鉛筆アイコンをクリックすると、編集用フォームと「決定」「キャンセル」ボタンが現れます。
削除ボタンをクリックしたときの画面
×アイコンをクリックすると、削除するかを問うアラート(モーダルウィンドウ)が表示されます。
「削除」をクリックすると、対象のメーカーが「dtb_maker」から削除されます。

まとめ&次のステップ

以上、新メニューからデータを追加・編集・削除できるようにするカスタマイズ方法を紹介しました。

ここまでで基本的な機能は実装できましたが、まだソート機能(並び替え機能)や、メーカーの個々のページは実装できていません。
引き続き、今後の記事で紹介していくつもりですので、ぜひ本記事と合わせてご覧ください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次