1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

プリザンターのv2テーマ切替ボタンを拡張機能で実装してみる

1
Posted at

はじめに

プリザンターには v2 テーマとして Cerulean(青)・Green Tea(緑)・Mandarin(橙)・Midnight(紫・ダーク)の 4 種類が用意されています。テーマはプロフィール編集画面から変更できますが、都度設定画面を開く手間があり気軽に試せません。

この記事では、画面左下に FAB(Floating Action Button) を配置し、クリックでメニューを展開して v2 テーマを即座に切り替えられる仕組みを、拡張スクリプトと拡張スタイルだけで実装します。テーマの切替にはプリザンター標準の $p.apiUsersUpdate を使用するため、サーバー側で設定が保存され、ブラウザやデバイスが変わっても設定が維持されます。

バージョン 1.4 以降の v2 テーマ(ceruleangreen-teamandarinmidnight)を対象にしています。

この機能はユーザーのプロフィール変更が許可されている環境が前提です。パラメータファイル App_Data/Parameters/Service.jsonShowProfilestrue になっていることを確認してください。false の場合、テーマ変更 API が利用できないため FAB メニューは動作しません。

この記事では Api.jsonCompatibility_1_3_12false(既定値)であることを前提としています。true の環境では $p.api~ 関数での ApiVersion 指定が正しく反映されません。
この機能はユーザーのプロフィール変更が許可されている環境が前提です。パラメータファイル App_Data/Parameters/Service.jsonShowProfilestrue に設定されていることを確認してください。無効の場合、テーマ変更 API が利用できないため FAB メニューは動作しません。パラメータ変更後はプリザンターの再起動が必要です。

仕組みを整理する

プロフィール編集のテーマ変更を調べる

まず、プロフィール編集画面でテーマを変更したときに何が起きているかを確認します。

プリザンターのナビゲーションメニューでは、アカウントメニューに「プロフィールの編集」リンクが表示されます。このリンク先は NavigationMenus.json で次のように定義されています。

{
  "MenuId": "AccountMenu_EditProfile",
  "Name": "EditProfile",
  "Icon": "ui-icon ui-icon-wrench",
  "LinkParams": [ "Users", "{UserId}", "Edit" ]
}

{UserId} はサーバー側で context.UserId に置換されるため、実際のリンク先は {ApplicationPath}users/{userId}/edit になります。

プロフィール編集画面には Users_Theme ドロップダウンがあり、テーマを選択して保存すると Users コントローラーの Update アクション が呼ばれます。

つまり、テーマの切替はプリザンター標準のユーザー更新 APIで行われています。FAB メニューからも $p.apiUsersUpdate を呼べば、プロフィール編集画面を開かずにテーマを切り替えられます。

実装のポイント

項目 内容
対象テーマ v2 テーマ(ceruleangreen-teamandarinmidnight
切替方式 $p.apiUsersUpdate でテーマを変更し、ページリロードで反映
ボタン配置 position: fixed で画面左下に固定する FAB
ボタン UI メインボタンをクリックするとテーマ選択ボタンが上方向に展開
アイコン Material Symbols(palette)+各テーマのカラーチップ
永続化 $p.apiUsersUpdate で Users テーブルに保存されるため、別途の保存処理は不要
テーマ判定 $p.apiUsersGet でユーザーの現在のテーマを取得

テーマの概要

各テーマのカラースキームは次のとおりです。

テーマ キー プライマリカラー 特徴
Cerulean cerulean #106ebe 標準の青系ライトテーマ
Green Tea green-tea #058266 落ち着いた緑系ライトテーマ
Mandarin mandarin #eb9151 暖かみのある橙系ライトテーマ
Midnight midnight #7a43b1 紫系のダークテーマ

FAB メニューの動作

メインボタンをクリックすると選択肢が展開され、テーマを選ぶとユーザー更新 API が呼ばれてページがリロードされます。

実装してみよう

拡張スタイルと拡張スクリプトの 2 ファイルで構成します。

拡張機能 役割
拡張スタイル FAB メニューの見た目
拡張スクリプト FAB メニューの生成・ユーザー更新 API によるテーマ切替

FAB メニューのスタイル(拡張スタイル)

拡張スタイルとして App_Data/Parameters/ExtendedStyles/ に配置します。テーマごとの CSS 変数上書きは不要です。テーマの切替はサーバー側で完結し、リロード後にプリザンターが正しいテーマの CSS を自動的に読み込みます。

ExtendedStyles/ThemeSwitcher.css
/* =============================================
   FAB メニュー(コンテナ)
   ============================================= */
.ts-fab {
  position: fixed;
  left: 24px;
  bottom: 24px;
  z-index: 900;
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 8px;
}

/* ── メインボタン ── */
.ts-fab-main {
  width: 48px;
  height: 48px;
  border: none;
  border-radius: 50%;
  background: var(--primaryColor, #106ebe);
  color: var(--invert-text, #fff);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35);
  transition: background 0.2s, transform 0.3s;
  padding: 0;
}

.ts-fab-main:hover {
  background: var(--primaryDark, #005a9e);
}

.ts-fab.is-open .ts-fab-main {
  transform: rotate(45deg);
}

.ts-fab-main .material-symbols-outlined {
  font-size: 24px;
}

/* ── 選択肢ボタン ── */
.ts-fab-item {
  width: 40px;
  height: 40px;
  border: 2px solid transparent;
  border-radius: 50%;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
  padding: 0;
  opacity: 0;
  transform: scale(0.3) translateY(20px);
  pointer-events: none;
  transition: opacity 0.25s, transform 0.25s, border-color 0.2s;
}

.ts-fab.is-open .ts-fab-item {
  opacity: 1;
  transform: scale(1) translateY(0);
  pointer-events: auto;
}

.ts-fab.is-open .ts-fab-item:nth-child(2) { transition-delay: 0.03s; }
.ts-fab.is-open .ts-fab-item:nth-child(3) { transition-delay: 0.06s; }
.ts-fab.is-open .ts-fab-item:nth-child(4) { transition-delay: 0.09s; }
.ts-fab.is-open .ts-fab-item:nth-child(5) { transition-delay: 0.12s; }

.ts-fab-item:hover {
  opacity: 0.85;
}

.ts-fab-item.is-active {
  border-color: var(--nonColor01, #1f1f1f);
  box-shadow: 0 0 0 2px var(--nonColor16, #fff), 0 2px 6px rgba(0, 0, 0, 0.35);
}

/* ── ツールチップ(選択肢の左横) ── */
.ts-fab-item::after {
  content: attr(data-label);
  position: absolute;
  left: 52px;
  white-space: nowrap;
  background: rgba(0, 0, 0, 0.75);
  color: #fff;
  font-size: 12px;
  padding: 4px 10px;
  border-radius: 4px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s;
}

.ts-fab-item:hover::after {
  opacity: 1;
}

FAB メニューの生成とテーマ切替(拡張スクリプト)

拡張スクリプトとして App_Data/Parameters/ExtendedScripts/ に配置します。

ExtendedScripts/ThemeSwitcher.js
$(function () {
  /* ── テーマ定義 ── */
  var themes = [
    { key: 'cerulean',  label: 'Cerulean',  color: '#106ebe' },
    { key: 'green-tea', label: 'Green Tea', color: '#058266' },
    { key: 'mandarin',  label: 'Mandarin',  color: '#eb9151' },
    { key: 'midnight',  label: 'Midnight',  color: '#7a43b1' }
  ];

  /* ── ユーザー情報を取得して現在のテーマを判定 ── */
  $p.apiUsersGet({
    id: $p.userId(),
    data: { ApiVersion: 1.1 },
    done: function (data) {
      var user = data.Response.Data[0];
      var currentKey = user.Theme || 'cerulean';
      buildFab(currentKey);
    }
  });

  /* ── FAB メニュー生成 ── */
  function buildFab(currentKey) {
    var $fab = $('<div>', { class: 'ts-fab' });

    // メインボタン
    var $main = $('<button>', {
      type: 'button',
      class: 'ts-fab-main',
      title: 'テーマ切替'
    }).append(
      $('<span>', { class: 'material-symbols-outlined', text: 'palette' })
    );

    $main.on('click', function (e) {
      e.stopPropagation();
      $fab.toggleClass('is-open');
    });

    $fab.append($main);

    // 選択肢ボタンを生成
    themes.forEach(function (theme) {
      var $item = $('<button>', {
        type: 'button',
        class: 'ts-fab-item' + (theme.key === currentKey ? ' is-active' : ''),
        title: theme.label,
        'data-key': theme.key,
        'data-label': theme.label
      }).css('background-color', theme.color);

      $item.on('click', function (e) {
        e.stopPropagation();
        var key = $(this).data('key');
        if (key === currentKey) {
          $fab.removeClass('is-open');
          return;
        }
        updateTheme(key);
      });

      $fab.append($item);
    });

    $('body').append($fab);

    // 外側クリックでメニューを閉じる
    $(document).on('click', function () {
      $fab.removeClass('is-open');
    });

    $fab.on('click', function (e) {
      e.stopPropagation();
    });
  }

  /* ── ユーザー更新 API でテーマを変更 ── */
  function updateTheme(key) {
    $p.apiUsersUpdate({
      id: $p.userId(),
      data: {
        ApiVersion: 1.1,
        Theme: key
      },
      done: function () {
        location.reload();
      },
      fail: function () {
        alert('テーマの変更に失敗しました。');
      }
    });
  }
});

各処理のポイントを見ていきましょう。

ユーザー情報の取得と現在のテーマ判定

$p.apiUsersGet で現在のユーザー情報を取得し、Theme フィールドから現在のテーマを判定します。ユーザー ID は $p.userId() で取得できます。

$p.apiUsersGet({
  id: $p.userId(),
  data: { ApiVersion: 1.1 },
  done: function (data) {
    var user = data.Response.Data[0];
    var currentKey = user.Theme || 'cerulean';
    buildFab(currentKey);
  }
});
関数 説明
$p.userId() 現在ログイン中のユーザー ID を返す
$p.apiUsersGet 指定ユーザーの情報を取得する標準関数
data.Response.Data[0].Theme ユーザーに設定されているテーマキー(未設定の場合は空文字列)

Theme が空文字列の場合はデフォルトの cerulean として扱います。$p.apiUsersGet の呼び出しに失敗した場合は FAB を生成しないため、プロフィール編集が許可されていない環境では自動的に非表示になります。

FAB メニューの動き

FAB メニューの構造は以下のとおりです。

.ts-fab(コンテナ:column-reverse で下から積む)
  ├─ .ts-fab-main(メインボタン:常時表示)
  ├─ .ts-fab-item[data-key="cerulean"]   背景色: #106ebe
  ├─ .ts-fab-item[data-key="green-tea"]  背景色: #058266
  ├─ .ts-fab-item[data-key="mandarin"]   背景色: #eb9151
  └─ .ts-fab-item[data-key="midnight"]   背景色: #7a43b1
操作 動作
メインボタンをクリック is-open クラスを付け替え、選択肢ボタンが上方向にアニメーション展開
選択肢ボタンをクリック API でテーマを更新 → ページリロード
同じテーマをクリック メニューを閉じるだけ(API 呼び出しなし)
外側をクリック メニューを閉じる(documentclick イベントで制御)

ユーザー更新 API の呼び出し

テーマの変更には $p.apiUsersUpdate を使います。

$p.apiUsersUpdate({
  id: $p.userId(),
  data: {
    ApiVersion: 1.1,
    Theme: key
  },
  done: function () {
    location.reload();
  },
  fail: function () {
    alert('テーマの変更に失敗しました。');
  }
});
パラメータ 説明
id $p.userId() で取得した現在のユーザー ID
data.Theme 切替先のテーマキー(ceruleangreen-teamandarinmidnight
done 更新成功時のコールバック
fail 更新失敗時のコールバック

API の成功コールバックで location.reload() を呼び出し、ページを自動的にリロードします。リロード後、プリザンターがサーバー側で新しいテーマの CSS を読み込むため、テーマが正しく反映されます。$p.apiUsersUpdate は内部で認証やリクエストトークンを処理するため、手動での CSRF トークン取得は不要です。

カスタマイズ

ボタンの位置を変える

FAB の位置は leftbottom の値を変更するだけで調整できます。

ExtendedStyles/ThemeSwitcher.css
 .ts-fab {
-  left: 24px;
-  bottom: 24px;
+  right: 24px;
+  bottom: 24px;
 }

まとめ

プリザンターの v2 テーマに、テーマ切替 FAB メニューを拡張スタイルと拡張スクリプトだけで追加しました。

  • 画面左下の FAB メニューをクリックすると 4 つのテーマ選択ボタンが展開し、各テーマのプライマリカラーがカラーチップとして表示される直感的な UI
  • $p.apiUsersGet で現在のテーマを取得し、$p.apiUsersUpdate でテーマを更新。サーバー側で Users テーブルの Theme 列が更新されるため、localStorage や Sessions API による保存は不要
  • API 成功後に location.reload() で自動リロードし、プリザンターが正しいテーマの CSS を読み込むため、テーマごとの CSS 変数上書きも不要
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?