佐藤 あゆみ
佐藤 あゆみ

necco Note

microCMSで、Studioや静的サイトに機能をちょい足し!「本日の営業時間」を表示してみよう

  • Web Development

この記事はmicroCMSでこんなことができた!あなたのユースケースを大募集 by microCMS Advent Calendar 2024」の参加記事です。

この投稿では、microCMSを活用して、Studio製のサイトに、「本日の営業時間」を表示する方法をご紹介します。
管理画面にmicroCMSを利用すれば、とても手軽に「オリジナル機能」をStudioサイトに追加できます。コードはJavaScriptですので、Studio以外の静的HTMLサイトなどでも流用可能です。
ノーコードツールだけではできないことも、microCMSと、コードの埋め込み機能を組み合わせれば実現できます。

※Studioはノーコードサービスですが、この記事は、HTMLやCSSがある程度分かる方向けにお届けしております🙇‍♀️一度構築してしまえば、microCMSの管理画面より、全くコードを触らずに更新可能です!

サンプルサイト

下記が今回制作したサンプルサイトです。フッター部分に営業時間を追記表示しています。
https://business-hours.studio.site/

サンプルサイトの制作には、Studioの無料テンプレート「日本茶カフェ」を利用しました。
日本茶カフェ|玉響茶園 | Studio Store

全体の設計

営業時間の表示機能をどのように組み立てていくか、下記のように考えました。 お店の営業時間は、店舗によっては結構ややこしいこともあり、昼営業や夜営業に分かれていたり、月曜日はこの時間、土日はあの時間と別々だったり、イベントによる特別系業ということもあります。そこで、下記のように考えてみました。

柔軟な営業時間設定

  • 曜日ごとに異なる営業時間に対応する
  • 祝日や特別なイベントの日、急な休みにも対応できるようにする
  • 「本日は○○のため、早じまいです」などの特別なお知らせも設定できるようにする

microCMSでらくらくデータ管理

  • 営業時間のデータ管理はすべてmicroCMSにおまかせ
  • 管理画面からポチポチ更新できる

Studioサイトでサクッと実装

  • Embedボックスでサクッと実装
  • ページを開いたその日の営業時間がリアルタイムで表示される

実はどこでも使える

  • Studio以外の静的HTMLサイトでも使えるJavaScriptコードにする

microCMSを設定する

それではさっそく、microCMSで管理画面を作成していきます。
https://microcms.io/

サービスを作成する

任意の名称で、サービスを作成します。
サービスの作成(microCMS公式ドキュメント)

APIを作成する

サービスが作成できたら、まずは、データを入力するためのAPIを作成します。
左のサイドバーの「コンテンツ(API)」の「+」をクリックします。

データ種別は「自分で決める」を選択します。

API名とエンドポイントを入力し、次に進みます。
今回は、API名を「営業時間」、エンドポイントを「business-hours」とします。

今回はAPIの型として「オブジェクト形式」を選択します。

次に、APIスキーマを定義します。

ここで入力欄の情報を入力できるのですが、今回は繰り返しフィールドやカスタムフィールドを利用するため、やや複雑な構造になり、一つ一つ設定するのは面倒です。
そこで、サクッと定義ファイルをインポートできる機能を利用しましょう!

下記の内容を、「api-business-hours.json」などの名前をつけ、JSONファイルとして保存してください。
(コード部分をトリプルクリックで全選択できます)

{"apiFields":[{"idValue":"Gbl1n3DxZV","fieldId":"specialSchedules","name":"特殊営業日と祝日","kind":"repeater","customFieldCreatedAtList":["2024-12-08T05:27:53.435Z"]},{"fieldId":"mon","name":"月曜日の営業時間","kind":"custom","required":true,"customFieldCreatedAt":"2024-12-08T05:40:08.393Z"},{"fieldId":"tue","name":"火曜日の営業時間","kind":"custom","required":true,"customFieldCreatedAt":"2024-12-08T05:40:08.393Z"},{"fieldId":"wed","name":"水曜日の営業時間","kind":"custom","required":true,"customFieldCreatedAt":"2024-12-08T05:40:08.393Z"},{"fieldId":"thu","name":"木曜日の営業時間","kind":"custom","required":true,"customFieldCreatedAt":"2024-12-08T05:40:08.393Z"},{"fieldId":"fri","name":"金曜日の営業時間","kind":"custom","required":true,"customFieldCreatedAt":"2024-12-08T05:40:08.393Z"},{"fieldId":"sat","name":"土曜日の営業時間","kind":"custom","required":true,"customFieldCreatedAt":"2024-12-08T05:40:08.393Z"},{"fieldId":"sun","name":"日曜日の営業時間","kind":"custom","required":true,"customFieldCreatedAt":"2024-12-08T05:40:08.393Z"},{"fieldId":"memo","name":"内部メモ","kind":"textArea","description":"外部には表示されないメモです。文例のコピペ用など自由にお使いください。"}],"customFields":[{"createdAt":"2024-12-08T05:27:53.435Z","fieldId":"irregularSchedule","name":"特殊予定日","fields":[{"idValue":"12Uk6cNd78","fieldId":"date","name":"日付","kind":"date","required":true},{"idValue":"wcZQGJW7Tj","fieldId":"description","name":"表示テキスト","kind":"text","required":true},{"idValue":"ZzV6PjsVL1","fieldId":"businessHours","name":"営業時間","kind":"text","required":false}],"position":[["12Uk6cNd78"],["wcZQGJW7Tj","ZzV6PjsVL1"]],"updatedAt":"2024-12-08T05:39:01.403Z","viewerGroup":"LrM"},{"createdAt":"2024-12-08T05:40:08.393Z","fieldId":"regularSchedule","name":"営業時間","fields":[{"idValue":"hjp15C9jZF","fieldId":"description","name":"表示テキスト","kind":"text","required":true},{"idValue":"f8BOkdUs3V","fieldId":"businessHours","name":"営業時間","kind":"text","required":false}],"position":[["hjp15C9jZF"],["f8BOkdUs3V"]],"updatedAt":"2024-12-08T05:44:45.482Z","viewerGroup":"LrM"}]}

ファイルインポートする場合は「こちら」のリンクをクリックして、ファイルを選択し、先ほど作成したJSONファイルをアップロードします。

たったこれだけで、営業時間の管理画面が完成しました🙌
コードを一行も書かずに、ログイン機構などを実装する必要もなく、パパッと専用の管理画面を用意できてしまうのがmicroCMSの素晴らしいところです。

通常営業日を入力する

入力欄ができましたので、まずは月〜日曜日までの営業予定を入力します(1)。
ページに表示したい文言と営業時間は、テキストでそのまま入力します。
営業有無をオンオフのトグルにしたり、営業時刻をプルダウンで選べるようにする方式も考えましたが、一番、柔軟性があって、分かりやすいのはテキスト入力だと考え、この形式にしました。

入力できたら、右上の「公開」ボタンを押して保存します(2)。

特殊営業日と祝日を入力する

次に、祝日などの特殊な営業時間の日を入力します。

「フィールドを追加」をクリックして、入力して、公開します。
繰り返しフィールドを利用しているので、好きな数だけ入力欄を追加できます。

※カレンダーUIに時刻が表示されているのが紛らわしいので「日付指定のみ」をONにする方が良かったなと後から思いました…。「カスタムフィールド」→「特殊予定日」→「スキーマ」→「日付」の「詳細設定」より、「日付指定のみ」をONに変更できます。

おまけ:内部メモについて

APIには「内部メモ」欄を設けています。毎年使う文言や、アイデアなどを、なんでもメモして置ける場所です。なくても良いのですが、あると便利です。

以上でmicroCMSでの設定は完了です🎉

microCMSは無料プランでもメンバーを3人まで招待できますので、管理者はもちろん、お店を運営される方を招待して、自由に更新してもらえます。

Studioでの実装

いよいよStudioで営業時間を表示します。
今回は「Embedボックス」機能を利用して、microCMSの情報をStudioで表示します。
Embedボックス機能は、Studioのページに任意のコードを埋め込める機能です。コードはSandboxモードで埋め込まれるため、ボックス外の領域には影響を及ぼすことなく、安全に埋め込めます。

サービスIDとAPIキーを取得する

microCMSのサービスIDとAPIキーを確認しましょう。
サービスIDは管理画面の左上に表示されています。

APIキーは、権限管理メニューのAPIキー管理からコピーできます。

※APIキーの権限は、デフォルトでは「GET(読み取りのみ可能)」な権限が設定されています。設定を変更するとPUTやPOSTなど、API経由で内容を書き換えられる権限にも変更可能です。Studioでの実装では、JavaScriptを利用して動的にデータを取得するため、APIキーが閲覧者に露出します。このため、書き換え可能な権限には変更せず、GET権限のままで利用しましょう。microCMSを有料プランにすれば、複数のAPIキーの設定が可能になり、APIキーを権限別で使い分けられるようになります。

サンプルコードを書き換える

下記が営業時間を表示するためのサンプルコードです。
「microCMSのサービスIDを入力する」「APIキーを入力する」とコメントのある部分をそれぞれ書き換えます。
また、サイトのデザインに応じて、CSSを書き換えます。

<div id="output" style="text-align:center;color:#333;background:#e7f8ee;padding:1em"></div>

<script src="<https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js>"></script>
<script src="<https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js>"></script>
<script src="<https://cdn.jsdelivr.net/npm/dayjs@1/locale/ja.js>"></script>
<script src="<https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js>"></script>
<script src="<https://cdn.jsdelivr.net/npm/dayjs@1/plugin/timezone.js>"></script>

<script>
const SERVICE_ID = '●●●●●●●'; // microCMSのサービスIDを入力する
const API_KEY = '●●●●●●●●●●●●●●●●●●'; // APIキーを入力する
const API_ENDPOINT = 'business-hours?fields=specialSchedules%2Cmon%2Ctue%2Cwed%2Cthu%2Cfri%2Csat%2Csun';
const TIMEZONE = 'Asia/Tokyo';
const WEEK_DAYS = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];

function setupDayjs() {
  dayjs.extend(dayjs_plugin_utc);
  dayjs.extend(dayjs_plugin_timezone);
  dayjs.locale('ja');
  dayjs.tz.setDefault(TIMEZONE);
}

async function fetchApiData() {
  const apiUrl = `https://${SERVICE_ID}.microcms.io/api/v1/${API_ENDPOINT}`;
  const headers = { 'X-MICROCMS-API-KEY': API_KEY };

  try {
    const response = await axios.get(apiUrl, { headers });
    return response.data;
  } catch (error) {
    console.error('APIからのデータ取得に失敗しました:', error);
    return null;
  }
}

function getBusinessHours(data, today, dayString) {
  const specialSchedule = data.specialSchedules.find(item => dayjs(item.date).format('YYYY-MM-DD') === today);
  if (specialSchedule) {
    return {
      description: specialSchedule.description,
      businessHours: specialSchedule.businessHours
    };
  }

  return data[dayString];
}

async function main() {
  setupDayjs();

  const data = await fetchApiData();
  if (!data) return;

  const now = dayjs();
  const today = now.format('YYYY-MM-DD');
  const dayString = WEEK_DAYS[now.day()];

  const schedule = getBusinessHours(data, today, dayString);

  const outputDiv = document.getElementById('output');
  outputDiv.innerHTML = `
    <p style="margin:0">${schedule.description}
    ${schedule.businessHours ? `<br/>
    <span style="font-size:80%">${schedule.businessHours}</span>` : ''}
  </p>`;
}

main();
</script>

StudioにEmbedボックスを設置する

Studioのデザインエディタで、追加パネルから、「Embedボックス」をスクリーンにドラッグドロップします。
Embedボックスを利用すると、任意のコードをページ内に埋め込めます。

埋め込みコードに、先ほど書き換えたコードをコピーペーストします。

Embedボックスの縦幅を「flex」に変更して、ボックスの高さが、内容物の縦幅と同じ高さになるようにします。

※もしボックスの縦幅が画面いっぱいに広がってしまう場合、EmbedボックスがBase直下に配置してあることが原因です。Embedボックスを一度グループ化して、Base直下ではなくしましょう

さらに背景色を透明に設定します。 適宜、マージンなどを調整します。

上記で完成です🎉

Studioのエディタ上では文字が表示されないことがありますが、ライブプレビューではきちんと表示されます。

ライブプレビューでも表示されない場合は、一度画面をリロードしてみましょう。Embedボックスの変更は即時反映されないことがあり、その場合はリロードすると解決できます。

Studioでの実装にカスタムコードを使う場合

Studioでは、有償プランの場合、上記のEmbedボックスを利用するほかにも、カスタムコード機能を使って、microCMSとの連携が可能です。カスタムコード機能は、ページの<head>要素内と、</body>タグ直前に、任意のコードを追加できる機能です。ボックスを埋め込むEmbedボックス機能とは異なり、ページ内の要素に自由にJavaScriptやCSSを適用できます。

カスタムコードを利用すれば、StudioのUIを使って「営業時間表示」をデザインできます。
実装の流れは、下記のようになります。

  1. StudioのUIで営業時間表示をデザインする
  2. デザインしたテキストに、StudioのUIからIDを付与する
  3. IDに対して、カスタムコードとして追加した<script>タグ内で、表示内容を書き換える処理を行う
  4. Studioのエディターやプレビュー表示にはカスタムコードが適用されないため、変更内容を公開して、公開ページから表示確認を行う

Embedボックスを使う場合

  • メリット:無料プランでも利用可能、プレビュー表示で動作を確認できる
  • デメリット:文言の見た目を変えたい場合、CSSでのカスタマイズが必要

カスタムコードを使う場合

  • メリット:文言のデザインを変えたい場合に、StudioのUIから変更可能
  • デメリット:有料プランのみで利用可能。Studio製サイトはSPAの挙動をとるため、ページ遷移時に工夫が必要なことも。具体的には、JavaScriptのMutationObserverで、指定の要素が出現したかどうかを監視するといった工夫をします。

その他の静的サイトで表示するには

Studio以外で利用したい場合は、サンプルコードを、営業時間を表示したい場所にHTMLコードとして貼り付けると、同じように動作します!

おまけ:営業日カレンダーを表示する

AIにお願いして、同じmicroCMSのAPIを利用して、営業日カレンダーを作ってもらいました。
新しいページを作成して、コードをembedボックスで貼り付ければ、表示できます。
よしなにCSSを設定すれば、見栄えもグッと良くなるでしょう。

<div id="calendar"></div>

<script>
const SERVICE_ID = '●●●●●●●'; // microCMSのサービスIDを入力する
const API_KEY = '●●●●●●●●●●●●●●●●●●'; // APIキーを入力する

async function fetchData() {
  const response = await fetch(`https://${SERVICE_ID}.microcms.io/api/v1/business-hours`, {
    headers: { 'X-API-KEY': API_KEY }
  });
  return await response.json();
}

function convertToJST(utcDateString) {
  const date = new Date(utcDateString);
  return new Date(date.getTime() + (9 * 60 * 60 * 1000));
}

function generateCalendars(data) {
  const calendarEl = document.getElementById('calendar');
  const now = new Date();
  const currentMonth = now.getMonth();
  const currentYear = now.getFullYear();

  const monthNames = ["1月", "2月", "3月", "4月", "5月", "6月",
    "7月", "8月", "9月", "10月", "11月", "12月"
  ];

  let html = '<div class="calendar-container">';
  
  html += generateMonthCalendar(data, currentYear, currentMonth, monthNames);
  
  const nextMonth = (currentMonth + 1) % 12;
  const nextMonthYear = currentMonth === 11 ? currentYear + 1 : currentYear;
  html += generateMonthCalendar(data, nextMonthYear, nextMonth, monthNames);
  
  html += '</div>';
  calendarEl.innerHTML = html;
}

function generateMonthCalendar(data, year, month, monthNames) {
  let html = `
    <div class="calendar">
      <div class="calendar-header">
        <h2>${year}年 ${monthNames[month]}</h2>
      </div>
      <table>
        <tr>
          <th>月</th><th>火</th><th>水</th><th>木</th><th>金</th><th>土</th><th>日</th>
        </tr>
  `;

  const daysInMonth = new Date(year, month + 1, 0).getDate();
  const firstDay = new Date(year, month, 1).getDay();
  const firstMonday = (firstDay === 0) ? 6 : firstDay - 1;

  let dayCount = 1;
  for (let i = 0; i < 6; i++) {
    html += '<tr>';
    for (let j = 0; j < 7; j++) {
      if ((i === 0 && j < firstMonday) || dayCount > daysInMonth) {
        html += '<td></td>';
      } else {
        const dayOfWeek = (j + 1) % 7;
        const dayInfo = getDayInfo(data, year, month, dayCount, dayOfWeek);
        html += `
          <td>
            <div class="day">${dayCount}</div>
            <div class="info">${dayInfo.description || ''}</div>
            <div class="hours">${dayInfo.businessHours || ''}</div>
          </td>
        `;
        dayCount++;
      }
    }
    html += '</tr>';
    if (dayCount > daysInMonth) break;
  }

  html += '</table></div>';
  return html;
}

function getDayInfo(data, year, month, day, dayOfWeek) {
  const dateString = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
  const specialDay = data.specialSchedules.find(s => {
    const jstDate = convertToJST(s.date);
    return jstDate.toISOString().startsWith(dateString);
  });

  if (specialDay) {
    return specialDay;
  }

  const weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
  return data[weekdays[dayOfWeek]] || {};
}

async function init() {
  const data = await fetchData();
  generateCalendars(data);
}

init();
</script>

<style>
.calendar-container {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
}

.calendar {
  width: 48%;
  margin-bottom: 20px;
}

.calendar-header {
  text-align: center;
  margin-bottom: 10px;
}

.calendar-header h2 {
  font-size: 1.2em;
  margin: 0;
}

.calendar table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
}

.calendar td {
  border: 1px solid #ccc;
  padding: 5px;
  vertical-align: top;
  height: 80px;
}

.day {
  font-weight: bold;
}

.info, .hours {
  font-size: 0.8em;
}

@media (max-width: 768px) {
  .calendar {
    width: 100%;
  }
}
</style>

※HTMLの<body>内に<style>要素を置くことは推奨されません(Metadata contentが期待される場所に置くべき)。Studioではない場所で利用する場合など、もし可能であれば、<style>要素を<head>タグ内に移動してご利用ください。

まとめ

今回は、ヘッドレスCMSであるmicroCMSと、ノーコードツールのStudioを組み合わせて、動的な営業時間表示機能を実装する方法をご紹介しました。

最近は、特にコロナ禍以降、人手不足などの理由もあってか、営業時間が流動的になる店舗が増えています。私自身が、ユーザーとして、「お店が開いているのか開いていないのかが分かりにくい」という悩みを抱えています。Googleでも検索結果にビジネスプロフィールで営業時間が表示されますが、ユーザーが登録したものなのか、お店がきちんと管理してるものなのかがイマイチ分からず、信用ならないと感じてしまうんですよね。
そんなときに公式サイトが、「今日、開いています!営業時間は、○時から○時までです」というお知らせをしてくれていたら、とても親切ではないでしょうか。そんな親切なサイトが増えて欲しいと思ってこの記事を書きました。
私がフリーランスとして活動していた際のお客さまのサイトにも、(Studioサイトではなかったため、PHPで制作したバージョンですが)実際に導入しています。

microCMSとJavaScriptを組み合わせたこの実装方法は、営業時間だけでなく、お知らせやイベント情報など、様々な動的コンテンツにも応用できます。StudioにもCMSはありますが、営業時間表示などのデータ加工には対応していません。また、スマホでの更新にも対応していません。microCMSなら、(実は)スマホでも更新できます。さらに、料金プランの都合で、Studioの無料プランやMiniプランにちょこっとだけmicroCMSを足して、コードの埋め込みで表示して運用する、といった使い方も考えられます。
ノーコードツールとヘッドレスCMSの組み合わせは、ウェブサイトの可能性を広げる便利な手段の一つになりそうです。

おすすめ記事

お仕事のご依頼・採用募集

📮 お仕事のご依頼やご相談、お待ちしております。

お仕事のご依頼やご相談は、お問い合わせ からお願いいたします。

🤝 一緒に働きませんか?

下記の職種を募集中です。より良いデザイン、言葉、エンジニアリングをチームで追求していける方をお待ちしております。詳細は 採用情報 をご覧ください。

  • デザイナー
  • アシスタントデザイナー
  • フロントエンドエンジニア
  • アシスタントフロントエンドエンジニア

🗒 会社案内資料もご活用ください。

弊社のサービスや制作・活動実績、会社概要、ご契約など各種情報をまとめた資料をご用意しています。会社案内資料 からダウンロード可能ですので、ぜひご活用ください。

株式会社necco ダウンロード資料へのバナー画像

(2024年10月時点)

佐藤 あゆみ

佐藤 あゆみ

Ayumi Sato

ニューヨーク生まれ。まもなく東京に移住し、1994年から2年間のオーストラリアでの生活を経て、ふたたび東京へ戻り、今も暮らす。1997年頃より趣味としてWeb制作を始め、以後も独学で学ぶ。 音楽専門学校中退後、音楽活動での成功を夢見ながら、PCパーツショップやバイク輸出入会社、楽器店など、掛け持ち含めて計20以上(?)の業種でアルバイトを重ね、ECサイトの運営管理や自社サーバの管理、プログラミングなども学ぶ。音楽活動を展開する中で、集客やフライヤー制作、プロモーションビデオ制作を行い、周辺技術を身につけるきっかけとなるも、2011年頃に区切りをつけ、ウェブ制作で生計を立てることを決意。その後は画廊やウェブ制作会社などで勤め、2014〜2022年まではフリーランスとして活動。2018年より、CSS NiteやBAU-YAなどのイベント、スクールにて登壇。2019年に「HTMLコーダー&ウェブ担当者のためのWebページ高速化超入門」を出版。 趣味はガジェットいじり&新しいサービスを試すこと。

SHARE

Other Note

necco Note

フロントエンドエンジニア志望の初学者からアシスタントにお勧めしたい課題図書5選!