GASを使ったモダンなUIのスケジュール管理アプリ開発ハンズオン

はじめに

Google Apps Script(GAS)は、Google Workspace上で動作するJavaScriptベースのスクリプトプラットフォームです。GASを使うことで、スプレッドシートやドキュメント、カレンダーといったGoogleサービスと連携した様々なアプリケーションを開発することができます。

近年、Web技術の発展に伴い、ユーザーインターフェース(UI)に求められるレベルも高くなってきています。従来のGASでは、UI構築に限界がありましたが、HTML、CSS、JavaScriptを組み合わせることで、モダンでリッチなUIを持つアプリケーションを開発することが可能になりました。

モダンなUIは、ユーザーエクスペリエンスを向上させ、データの視覚化を改善し、ユーザーエンゲージメントを高めるなど、多くの利点があります。 例えば、カレンダーアプリであれば、予定が見やすく、操作しやすいインターフェースを提供することで、ユーザーの利便性を高めることができます。

本ガイドでは、GASを使ってモダンなUIのスケジュール管理アプリを作るハンズオンを通して、GASの基礎知識からモダンなUI構築、そしてGAS APIを使ったGoogleサービスとの連携までを学びます。ハンズオン形式で進めることで、実際に手を動かしながらGAS開発のスキルを習得することができます。

Google Apps Script(GAS)とは

Google Apps Script(GAS)は、Google Workspace上で動作するJavaScriptベースのスクリプトプラットフォームです。GASを使うことで、スプレッドシートやドキュメント、カレンダーといったGoogleサービスと連携した様々なアプリケーションを開発することができます。GASは、サーバーレスで動作するため、インフラストラクチャの管理が不要で、手軽にアプリケーション開発を始められます。 Googleサービスとの連携が容易で、開発時間を短縮できるなど、従来のWebアプリケーション開発と比べて多くの利点があります。

GASは、例えば以下の用途で使用されます。

  • Google Workspaceの自動化:スプレッドシートのデータ処理、ドキュメントの自動生成、メールの自動送信など、Google Workspaceの様々な操作を自動化することができます。
  • Webアプリケーションの開発:HTML、CSS、JavaScriptと組み合わせることで、Webアプリケーションを開発することができます。GASでは、HTML Serviceを使ってHTMLを生成し、UIを構築することができます。
  • Googleサービスとの連携:GASは、Google Calendar API、Gmail API、Drive APIなど、様々なGoogleサービスのAPIを提供しています。これらのAPIを使うことで、Googleサービスと連携したアプリケーションを開発することができます。
  • 外部サービスとの連携:GASは、UrlFetchAppを使って外部のWebサービスと連携することもできます。

モダンなUIを実現するためのHTML、CSS、JavaScriptの基礎知識

モダンなUIを実現するためには、HTML、CSS、JavaScriptの基礎知識が不可欠です。ここでは、それぞれの役割と基本的な使い方について解説します。

HTML

HTML(HyperText Markup Language)は、Webページの構造を記述するためのマークアップ言語です。HTMLでは、見出し、段落、リスト、画像といった要素をタグを使って記述することで、Webページの内容を表現します。 (MDN Web Docs)

基本的なHTMLの構造

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ページタイトル</title>
</head>
<body>
  <h1>見出し</h1>
  <p>段落</p>
</body>
</html>

CSS

CSS(Cascading Style Sheets)は、Webページの見た目を装飾するための言語です。CSSでは、要素の色、サイズ、配置などを指定することで、Webページのデザインを調整します。(W3Schools)

CSSの基本的な記述方法

h1 {
  color: blue; /* 見出しの色を青に設定 */
  font-size: 24px; /* 見出しのサイズを24pxに設定 */
}

JavaScript

JavaScriptは、Webページに動的な要素を追加するためのスクリプト言語です。JavaScriptを使うことで、ボタンクリック時の処理、アニメーションの実装、データの取得など、Webページに様々な機能を追加することができます。(MDN Web Docs)

JavaScriptの基本的な記述方法

alert("Hello, world!"); // アラートダイアログを表示

ハンズオン

このハンズオンでは、モダンなUIを備えたスケジュール管理アプリをGASで開発します。目標は、基本的なスケジュール管理機能に加え、Googleサービスとの連携による拡張性も持たせたアプリを作ることです。

主な機能

  • スケジュールの登録、編集、削除: ユーザーが自身のスケジュールを管理できる基本的な機能です。
  • 複数のカレンダーとの同期: Googleカレンダーの複数カレンダーと同期し、スケジュールを一元管理できます。
  • リマインダー機能: 予定を事前に通知する機能です。
  • 他のユーザーとの共有機能: 他のユーザーとスケジュールを共有し、共同作業を円滑にします。

ハンズオンの進め方

ハンズオンは、以下の手順で進めます。

  1. GASプロジェクトの作成: まず、GASの新しいプロジェクトを作成し、スクリプトエディタを開きます。
  2. HTML、CSS、JavaScriptを使ったUIの作成: HTMLでアプリの骨組みを作り、CSSで見た目を整え、JavaScriptで動的な要素を追加します。
    • HTMLでは、フォーム (<form>, <input>, <button>) を使用してスケジュールの入力を受け付け、カレンダーを表示する領域 (<div>) を用意します。
    • CSSを用いて、これらの要素の色、フォント、配置などを調整し、視覚的に appealing なインターフェースを作成します。レスポンシブデザインを取り入れることで、様々なデバイスで快適に利用できるアプリを目指します。
    • JavaScriptは、ユーザー入力の処理、データの検証、GAS関数とのやり取り、UIの動的な更新などに使用します。例えば、フォームの送信イベントを処理し、入力されたスケジュールデータをGAS側に送信するコードを記述します。
  3. GASのAPIを使ったカレンダー操作の実装: GASのCalendar APIを使って、Googleカレンダーと連携する機能を実装します。
    • CalendarApp を使用してカレンダーを取得し、 createEvent, getEvents などのメソッドで予定の作成、取得を行います。
  4. 複数のカレンダーとの同期機能の実装: 複数のカレンダーとスケジュールを同期する機能を実装します。
    • CalendarApp.getAllCalendars() を使用してユーザーがアクセス可能なすべて のカレンダーを取得し、選択したカレンダーと同期できるようにします。
  5. リマインダー機能の実装: 指定した時間にリマインダーを通知する機能を実装します。
    • ScriptApp.newTrigger("関数名").timeBased().atHour(時間).everyDays(日数).create() を使用して、指定した時間にトリガーを設定し、リマインダー通知の処理を実行します。
  6. 他のユーザーとの共有機能の実装: 他のユーザーとスケジュールを共有する機能を実装します。
    • CalendarApp.Calendar クラスの setGuestsCanSeeGuests(true)setGuestsCanModify(true) などのメソッドを使用して、カレンダーの共有設定を操作します。

GASのAPIの使い方

GASでは、様々なGoogleサービスのAPIを提供しています。ここでは、スケジュール管理アプリで使用するカレンダーAPIの使い方について解説します。

CalendarApp

CalendarAppは、Googleカレンダーを操作するためのAPIです。CalendarAppを使うことで、以下の操作を行うことができます。

  • カレンダーの取得:CalendarApp.getDefaultCalendar()
  • すべてのカレンダーの取得: CalendarApp.getAllCalendars()
  • 予定の作成:calendar.createEvent(title, startTime, endTime, options)
  • 予定の取得:calendar.getEvents(startTime, endTime)
  • 予定の更新: event.setTitle(title), event.setTime(startTime, endTime) など
  • 予定の削除: event.deleteEvent()

予定の作成

function createEvent() {
  // カレンダーを取得
  const calendar = CalendarApp.getDefaultCalendar();
  // 予定を作成
  calendar.createEvent(
    '会議', // 予定のタイトル
    new Date('2023-12-24T09:00:00'), // 開始日時
    new Date('2023-12-24T10:00:00'), // 終了日時
    {location: '会議室A'} // オプション
  );
}

TypeScript の使用

GASの開発にTypeScriptを使用することで、コードの保守性向上とエラー削減に繋がります。 TypeScriptはJavaScriptに静的型付けを追加した言語で、大規模なプロジェクトや長期的なメンテナンスが必要な場合に特に有効です。

TypeScriptの開発環境設定

  1. clasp (Command Line Apps Script Projects) をインストールします。claspは、GASプロジェクトをローカル環境で管理するためのツールです。
  2. ローカル環境にGASプロジェクトのフォルダを作成し、clasp create コマンドでGASプロジェクトを作成します。
  3. npm init -y コマンドでpackage.jsonを作成し、 npm install --save-dev typescript @types/google-apps-script コマンドでTypeScriptとGASの型定義ファイルをインストールします。
  4. tsconfig.jsonを作成し、TypeScriptのコンパイルオプションを設定します。
  5. .ts 拡張子のTypeScriptファイルを作成し、GASのコードを記述します。
  6. clasp push コマンドでTypeScriptのコードをGASプロジェクトにアップロードします。

外部ライブラリとフレームワークの活用

GASの開発では、外部ライブラリやフレームワークを活用することで、開発を効率化し、より高度な機能を簡単に実装することができます。

gas-db

gas-db は、Googleスプレッドシートをデータベースのように扱うためのライブラリです。 スプレッドシートのデータを簡単に取得、更新、削除することができ、GASアプリケーションでのデータ管理を簡素化します。

gas-dbの使用方法

  1. gas-db ライブラリをGASプロジェクトにインストールします。
  2. gas-db を使用してスプレッドシートのデータにアクセスするためのコードを記述します。

HTMLダイアログの利用

HTMLダイアログは、ユーザーからの入力を受け取ったり、確認メッセージを表示したりする際に役立ちます。 GASでは、 HtmlService.createHtmlOutputFromFile を使用してHTMLダイアログを作成し、表示することができます。

HTMLダイアログの作成

function showDialog() {
  const html = HtmlService.createHtmlOutputFromFile('dialog.html');
  SpreadsheetApp.getUi().showModalDialog(html, 'ダイアログのタイトル');
}

サンプルコード

GASを使ったモダンなUIのスケジュール管理アプリのサンプルコードを5つ紹介します。各サンプルコードは、スプレッドシート、ドキュメント、カレンダー、サイト、フォームとの連携方法、モダンなUIを実現するためのHTML、CSS、JavaScriptの記述、GASのAPIを使ったデータの取得、更新、削除などの処理、コードの解説とコメントアウトを含んでいます。

これらのサンプルコードは、ハンズオンで作成するスケジュール管理アプリの土台となるものです。それぞれのサンプルコードで紹介する技術や機能を組み合わせることで、より高度なスケジュール管理アプリを開発することができます。

サンプルコード1:シンプルなスケジュール登録アプリ

このサンプルコードでは、HTML Serviceを使ってシンプルなUIを作成し、GASのCalendar APIを使ってGoogleカレンダーに予定を登録するアプリを作成します。

HTML (index.html)

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <style>
    /* CSS */
  </style>
</head>
<body>
  <h1>スケジュール登録</h1>
  <form id="scheduleForm">
    <label for="title">タイトル:</label>
    <input type="text" id="title" name="title"><br><br>
    <label for="startDate">開始日時:</label>
    <input type="datetime-local" id="startDate" name="startDate"><br><br>
    <label for="endDate">終了日時:</label>
    <input type="datetime-local" id="endDate" name="endDate"><br><br>
    <button type="submit">登録</button>
  </form>
  <script>
    // JavaScript
    document.getElementById('scheduleForm').addEventListener('submit', function(e) {
      e.preventDefault();
      // フォームの値を取得
      const title = document.getElementById('title').value;
      const startDate = document.getElementById('startDate').value;
      const endDate = document.getElementById('endDate').value;
      // GASの関数を実行
      google.script.run.createSchedule(title, startDate, endDate);
    });
  </script>
</body>
</html>

GAS (code.gs)

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function createSchedule(title, startDate, endDate) {
  // カレンダーを取得
  const calendar = CalendarApp.getDefaultCalendar();
  // 予定を作成
  calendar.createEvent(title, new Date(startDate), new Date(endDate));
}

サンプルコード2:スプレッドシートと連携したスケジュール管理アプリ

このサンプルコードでは、スプレッドシートに保存されているスケジュールデータをGASで取得し、HTML Serviceを使ってカレンダー形式で表示するアプリを作成します。

HTML (index.html)

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <style>
    /* CSS */
  </style>
</head>
<body>
  <h1>スケジュール管理</h1>
  <div id="calendar"></div>
  <script>
    // JavaScript
    google.script.run.withSuccessHandler(function(schedules) {
      // カレンダーを表示
      // ...
    }).getSchedules();
  </script>
</body>
</html>

GAS (code.gs)

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function getSchedules() {
  // スプレッドシートを取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('スケジュール');
  // スケジュールデータを取得
  // ...
  return schedules; // schedules 変数にデータが格納されている想定
}

サンプルコード3:ドキュメントと連携した議事録作成アプリ

このサンプルコードでは、Googleカレンダーの予定を取得し、議事録のテンプレートを元にGoogleドキュメントを自動生成するアプリを作成します。

GAS (code.gs)

function createMinutes() {
  // カレンダーを取得
  const calendar = CalendarApp.getDefaultCalendar();
  // 予定を取得 (例: 今日の予定)
  const events = calendar.getEvents(new Date(), new Date()); // 必要に応じて期間を調整
  // 議事録のテンプレートを取得 (テンプレートIDを実際のIDに置き換えてください)
  const templateDoc = DriveApp.getFileById('テンプレートID');
  // 予定ごとに議事録を作成
  events.forEach(function(event) {
    // ドキュメントを複製
    const newDoc = templateDoc.makeCopy(event.getTitle() + ' 議事録');
    // 議事録の内容を編集
    // 例: const body = newDoc.getBody();
    // body.replaceText('{予定タイトル}', event.getTitle());
    // ...
  });
}

サンプルコード4:サイトと連携した情報表示アプリ

このサンプルコードでは、外部サイトから情報を取得し、HTML Serviceを使ってWebページに表示するアプリを作成します。

HTML (index.html)

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <style>
    /* CSS */
  </style>
</head>
<body>
  <h1>情報表示</h1>
  <div id="information"></div>
  <script>
    // JavaScript
    google.script.run.withSuccessHandler(function(information) {
      // 情報を表示
      // document.getElementById('information').innerHTML = information;
      // ...
    }).getInformation();
  </script>
</body>
</html>

GAS (code.gs)

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function getInformation() {
  // 外部サイトから情報を取得 (UrlFetchAppを使用)
  // const response = UrlFetchApp.fetch("外部サイトのURL");
  // const information = response.getContentText();
  // ...
  return information; // information 変数にデータが格納されている想定
}

サンプルコード5:フォームと連携したアンケート集計アプリ

このサンプルコードでは、Googleフォームの回答を取得し、HTML Serviceを使って集計結果を表示するアプリを作成します。

HTML (index.html)

<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <style>
    /* CSS */
  </style>
</head>
<body>
  <h1>アンケート集計結果</h1>
  <div id="results"></div>
  <script>
    // JavaScript
    google.script.run.withSuccessHandler(function(results) {
      // 集計結果を表示
      // document.getElementById('results').innerHTML = JSON.stringify(results);
      // ...
    }).getResults();
  </script>
</body>
</html>

GAS (code.gs)

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function getResults() {
  // フォームを取得 (フォームIDを指定するか、アクティブなフォームを取得)
  const form = FormApp.getActiveForm(); // もしくは FormApp.openById('フォームID');
  // 回答を取得
  const responses = form.getResponses();
  // 集計結果を計算
  let results = {}; // 集計ロジックをここに追加
  // ...
  return results;
}

まとめ

本ガイドでは、GASを使ったモダンなUIのスケジュール管理アプリ開発ハンズオンを通して、GASの基礎知識からモダンなUI構築、そしてGAS APIを使ったGoogleサービスとの連携までを学びました。ハンズオン形式で進めることで、実際に手を動かしながらGAS開発のスキルを習得することができたかと思います。

ハンズオンでは、HTML、CSS、JavaScriptを駆使してモダンなUIを構築し、GASのCalendar APIを使ってGoogleカレンダーと連携する機能を実装しました。さらに、スプレッドシートをデータベースとして活用したり、外部ライブラリを導入したりすることで、GASの拡張性を体験することができました。

GASは、Google Workspaceと連携した様々なアプリケーションを開発できる強力なツールです。本ガイドで学んだ知識を活かして、ぜひ独自のGASアプリケーション開発に挑戦してみてください。

参考にしたサイトのリスト

  • GASでGoogleカレンダーに予定を登録する方法【GAS入門】
  • Google Apps Script で スプレッドシートを DB 的に扱うライブラリを作った
  • Google Apps Script の開発環境をモダンにする
  • GAS でダイアログにフォームを設置してユーザーからの入力を受け取る
  • 非エンジニアがGASに入門するならこれ!スプレッドシートを操作して業務効率化
  • Googleカレンダーの予定をLINEで通知する方法【GAS】
  • 【GAS】Google Calendar APIを使って予定の登録・取得をしてみる
  • Flutter Web × LIFF で作る!GAS で LINE Bot 連携したスケジュール管理アプリ
  • 【GAS】スプレッドシートのタスクをGoogleカレンダーに自動登録する方法
  • 【GAS】Googleカレンダーとスプレッドシートを連携する方法を解説

引用文献

  1. あなたもできる!GASで勤怠入力Slack App構築, https://weseek.co.jp/tech/1494/
  2. Google Apps Scriptのモダンな開発環境を求めて – kenchan0130 blog, https://kenchan0130.github.io/post/2018-12-03-1
  3. Google Apps Script + gas-db + HTML を使った TODO アプリ – Zenn, https://zenn.dev/shunta_furukawa/articles/d99b2635679dfd
  4. GASでシンプルなフォームつきダイアログを実装する – Zenn, https://zenn.dev/zaki_yama/articles/gas-form-dialog
  5. 3 時間で学ぶ Google Apps Script 入門 ~スプレッドシートと連携 …, https://www.seplus.jp/dokushuzemi/blog/2022/03/learn_gas4non_developers.html
  6. GAS×LINEでGoogleカレンダー当日の予定を自動通知する方法 …, https://feynman.co.jp/it-tool-takumi/google-calendar-line-api-schedule-notification/
  7. 【サンプルコード付き】GASでGoogle Calendarを操作する方法を …, https://tech-lab.sios.jp/archives/45556
  8. 【コード解説:GAS】スケジュール一覧取得/スケジュールの作成API …, https://zenn.dev/tokku5552/books/flutter-web-liff-scheduler/viewer/9_gas
  9. GAS活用!スプレッドシートのタスク表をGoogleカレンダーに …, https://feynman.co.jp/it-tool-takumi/google-calendar-tasks/
  10. 【GAS】Googleカレンダー×スプレッドシートでスケジュール管理 …, https://yamady-works.com/googlecalendar-gas/54/

Canvasエクスポート

WordPressだとカスタムHTML表示うまくいかないからHTML置いておく

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GASスケジュール管理アプリ開発ガイド</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body { font-family: 'Inter', sans-serif; }
        .tab-content { display: none; }
        .tab-content.active { display: block; }
        .tab-button.active { background-color: #3b82f6; color: white; }
        .tab-button { transition: background-color 0.3s ease; }
        .code-block { background-color: #1e293b; color: #e2e8f0; padding: 1rem; border-radius: 0.5rem; overflow-x: auto; margin-top: 0.5rem; margin-bottom: 1rem; font-family: 'Courier New', Courier, monospace; }
        .code-block pre { margin: 0; }
        .copy-button { background-color: #4f46e5; color: white; padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-size: 0.875rem; cursor: pointer; float: right; margin-bottom: 0.5rem;}
        .copy-button:hover { background-color: #4338ca; }
        .section-title { font-size: 1.5rem; font-weight: 600; margin-bottom: 1rem; color: #1f2937; border-bottom: 2px solid #3b82f6; padding-bottom: 0.5rem;}
        .sub-section-title { font-size: 1.25rem; font-weight: 600; margin-top: 1.5rem; margin-bottom: 0.75rem; color: #374151;}
        .content-container { max-width: 1000px; margin: 0 auto; padding: 2rem 1rem;}
        .nav-container { background-color: #f3f4f6; padding: 1rem 0; border-bottom: 1px solid #d1d5db; }
        .nav-tabs { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem; padding: 0 1rem;}
        .link-list li { margin-bottom: 0.5rem; }
        .link-list a { color: #2563eb; text-decoration: underline; }
        .link-list a:hover { color: #1d4ed8; }

        @media (max-width: 768px) {
            .nav-tabs { flex-direction: column; align-items: stretch; }
            .tab-button { width: 100%; text-align: center; margin-bottom: 0.25rem; }
        }
    </style>
</head>
<body class="bg-stone-100 text-stone-800">
    <div class="content-container">
        <header class="text-center mb-8">
            <h1 class="text-4xl font-bold text-sky-700">GASを使ったモダンなUIのスケジュール管理アプリ開発ハンズオン</h1>
            <p class="text-lg text-stone-600 mt-2">Google Apps Scriptの基礎から応用までをインタラクティブに学ぼう</p>
        </header>

        <div class="nav-container mb-8 shadow-md rounded-lg">
            <nav class="nav-tabs" id="tab-navigation">
                <button class="tab-button active py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('overview')">概要</button>
                <button class="tab-button py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('ui-basics')">UI基礎</button>
                <button class="tab-button py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('hands-on')">ハンズオン</button>
                <button class="tab-button py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('gas-api')">GAS API</button>
                <button class="tab-button py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('advanced-topics')">発展</button>
                <button class="tab-button py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('sample-code')">サンプルコード</button>
                <button class="tab-button py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('conclusion')">まとめ</button>
                <button class="tab-button py-2 px-4 bg-white hover:bg-sky-100 text-sky-700 rounded-md shadow" onclick="showTab('references')">参考資料</button>
            </nav>
        </div>

        <main id="main-content" class="bg-white p-6 sm:p-8 rounded-lg shadow-lg">
            <section id="overview" class="tab-content active">
                <h2 class="section-title">概要</h2>
                <p class="mb-4">このセクションでは、Google Apps Script(GAS)の基本的な紹介と、モダンなユーザーインターフェース(UI)がアプリケーション開発においてなぜ重要なのかについて説明します。GASの可能性と、本ガイドで学ぶことの概要を把握しましょう。</p>
                <div class="bg-sky-50 p-4 rounded-lg border border-sky-200">
                    <h3 class="sub-section-title">はじめに</h3>
                    <p class="mb-3">Google Apps Script(GAS)は、Google Workspace上で動作するJavaScriptベースのスクリプトプラットフォームです。GASを使うことで、スプレッドシートやドキュメント、カレンダーといったGoogleサービスと連携した様々なアプリケーションを開発することができます。</p>
                    <p class="mb-3">近年、Web技術の発展に伴い、ユーザーインターフェース(UI)に求められるレベルも高くなってきています。従来のGASでは、UI構築に限界がありましたが、HTML、CSS、JavaScriptを組み合わせることで、モダンでリッチなUIを持つアプリケーションを開発することが可能になりました。</p>
                    <p class="mb-3">モダンなUIは、ユーザーエクスペリエンスを向上させ、データの視覚化を改善し、ユーザーエンゲージメントを高めるなど、多くの利点があります。 例えば、カレンダーアプリであれば、予定が見やすく、操作しやすいインターフェースを提供することで、ユーザーの利便性を高めることができます。</p>
                    <p>本ガイドでは、GASを使ってモダンなUIのスケジュール管理アプリを作るハンズオンを通して、GASの基礎知識からモダンなUI構築、そしてGAS APIを使ったGoogleサービスとの連携までを学びます。ハンズオン形式で進めることで、実際に手を動かしながらGAS開発のスキルを習得することができます。</p>

                    <h3 class="sub-section-title">Google Apps Script(GAS)とは</h3>
                    <p class="mb-3">Google Apps Script(GAS)は、Google Workspace上で動作するJavaScriptベースのスクリプトプラットフォームです。GASを使うことで、スプレッドシートやドキュメント、カレンダーといったGoogleサービスと連携した様々なアプリケーションを開発することができます。GASは、サーバーレスで動作するため、インフラストラクチャの管理が不要で、手軽にアプリケーション開発を始められます。 Googleサービスとの連携が容易で、開発時間を短縮できるなど、従来のWebアプリケーション開発と比べて多くの利点があります。</p>
                    <p class="mb-2 font-semibold">GASは、例えば以下の用途で使用されます。</p>
                    <ul class="list-disc list-inside ml-4 space-y-1">
                        <li><strong>Google Workspaceの自動化</strong>:スプレッドシートのデータ処理、ドキュメントの自動生成、メールの自動送信など、Google Workspaceの様々な操作を自動化することができます。</li>
                        <li><strong>Webアプリケーションの開発</strong>:HTML、CSS、JavaScriptと組み合わせることで、Webアプリケーションを開発することができます。GASでは、HTML Serviceを使ってHTMLを生成し、UIを構築することができます。</li>
                        <li><strong>Googleサービスとの連携</strong>:GASは、Google Calendar API、Gmail API、Drive APIなど、様々なGoogleサービスのAPIを提供しています。これらのAPIを使うことで、Googleサービスと連携したアプリケーションを開発することができます。</li>
                        <li><strong>外部サービスとの連携</strong>:GASは、<code>UrlFetchApp</code>を使って外部のWebサービスと連携することもできます。</li>
                    </ul>
                </div>
            </section>

            <section id="ui-basics" class="tab-content">
                <h2 class="section-title">UI基礎</h2>
                <p class="mb-4">モダンで魅力的なユーザーインターフェースを構築するために不可欠な、HTML、CSS、JavaScriptの基本を学びます。これらの技術がどのように連携し、GASアプリケーションのフロントエンドを形成するのかを理解しましょう。</p>
                <div class="bg-emerald-50 p-4 rounded-lg border border-emerald-200">
                    <h3 class="sub-section-title">HTML</h3>
                    <p class="mb-2">HTML(HyperText Markup Language)は、Webページの構造を記述するためのマークアップ言語です。HTMLでは、見出し、段落、リスト、画像といった要素をタグを使って記述することで、Webページの内容を表現します。</p>
                    <p class="font-semibold">基本的なHTMLの構造:</p>
                    <div class="code-block">
<button class="copy-button" onclick="copyCode(this)">コピー</button>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;title&gt;ページタイトル&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;見出し&lt;/h1&gt;
  &lt;p&gt;段落&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
                    </div>

                    <h3 class="sub-section-title">CSS</h3>
                    <p class="mb-2">CSS(Cascading Style Sheets)は、Webページの見た目を装飾するための言語です。CSSでは、要素の色、サイズ、配置などを指定することで、Webページのデザインを調整します。</p>
                    <p class="font-semibold">CSSの基本的な記述方法:</p>
                    <div class="code-block">
<button class="copy-button" onclick="copyCode(this)">コピー</button>
<pre><code class="language-css">h1 {
  color: blue; /* 見出しの色を青に設定 */
  font-size: 24px; /* 見出しのサイズを24pxに設定 */
}</code></pre>
                    </div>

                    <h3 class="sub-section-title">JavaScript</h3>
                    <p class="mb-2">JavaScriptは、Webページに動的な要素を追加するためのスクリプト言語です。JavaScriptを使うことで、ボタンクリック時の処理、アニメーションの実装、データの取得など、Webページに様々な機能を追加することができます。</p>
                    <p class="font-semibold">JavaScriptの基本的な記述方法:</p>
                    <div class="code-block">
<button class="copy-button" onclick="copyCode(this)">コピー</button>
<pre><code class="language-javascript">alert("Hello, world!"); // アラートダイアログを表示</code></pre>
                    </div>
                </div>
            </section>

            <section id="hands-on" class="tab-content">
                <h2 class="section-title">ハンズオン:スケジュール管理アプリ開発</h2>
                <p class="mb-4">このセクションでは、実際にGASを使ってモダンなUIのスケジュール管理アプリを開発する手順を解説します。アプリの主な機能、設計、そして開発ステップを順を追って見ていきましょう。</p>
                <div class="bg-amber-50 p-4 rounded-lg border border-amber-200">
                    <h3 class="sub-section-title">主な機能</h3>
                    <ul class="list-disc list-inside ml-4 mb-4 space-y-1">
                        <li>スケジュールの登録、編集、削除</li>
                        <li>複数のGoogleカレンダーとの同期</li>
                        <li>リマインダー機能</li>
                        <li>他のユーザーとの共有機能</li>
                    </ul>

                    <h3 class="sub-section-title">ハンズオンの進め方</h3>
                    <ol class="list-decimal list-inside ml-4 space-y-2">
                        <li>
                            <strong>GASプロジェクトの作成:</strong>
                            <p class="ml-4 text-sm text-gray-700">まず、GASの新しいプロジェクトを作成し、スクリプトエディタを開きます。</p>
                        </li>
                        <li>
                            <strong>HTML、CSS、JavaScriptを使ったUIの作成:</strong>
                            <p class="ml-4 text-sm text-gray-700">HTMLでアプリの骨組みを作り、CSSで見た目を整え、JavaScriptで動的な要素を追加します。フォーム要素(&lt;form&gt;, &lt;input&gt;, &lt;button&gt;)で入力を受け付け、&lt;div&gt;でカレンダー表示領域を設けます。レスポンシブデザインを意識し、様々なデバイスでの快適な利用を目指します。JavaScriptでユーザー入力の処理、データ検証、GAS関数連携、UIの動的更新を行います。</p>
                        </li>
                        <li>
                            <strong>GASのAPIを使ったカレンダー操作の実装:</strong>
                            <p class="ml-4 text-sm text-gray-700">GASのCalendar API(<code>CalendarApp</code>)を使い、Googleカレンダー連携機能を実装します。<code>createEvent</code>, <code>getEvents</code>メソッドなどで予定の作成・取得を行います。</p>
                        </li>
                        <li>
                            <strong>複数のカレンダーとの同期機能の実装:</strong>
                            <p class="ml-4 text-sm text-gray-700"><code>CalendarApp.getAllCalendars()</code>でユーザーがアクセス可能な全カレンダーを取得し、選択したカレンダーと同期できるようにします。</p>
                        </li>
                        <li>
                            <strong>リマインダー機能の実装:</strong>
                            <p class="ml-4 text-sm text-gray-700"><code>ScriptApp.newTrigger("関数名").timeBased().atHour(時間).everyDays(日数).create()</code>で指定時間にトリガーを設定し、リマインダー通知を実行します。</p>
                        </li>
                        <li>
                            <strong>他のユーザーとの共有機能の実装:</strong>
                            <p class="ml-4 text-sm text-gray-700"><code>CalendarApp.Calendar</code>クラスの<code>setGuestsCanSeeGuests(true)</code>や<code>setGuestsCanModify(true)</code>などでカレンダーの共有設定を操作します。</p>
                        </li>
                    </ol>
                </div>
            </section>

            <section id="gas-api" class="tab-content">
                <h2 class="section-title">GAS APIの使い方</h2>
                <p class="mb-4">Google Apps Scriptが提供する強力なAPI、特にGoogleカレンダーを操作するための<code>CalendarApp</code>サービスに焦点を当てます。基本的なAPIの使用方法を学び、スケジュール管理アプリに機能を組み込みましょう。</p>
                <div class="bg-indigo-50 p-4 rounded-lg border border-indigo-200">
                    <h3 class="sub-section-title">CalendarApp</h3>
                    <p class="mb-2"><code>CalendarApp</code>は、Googleカレンダーを操作するためのAPIです。<code>CalendarApp</code>を使うことで、以下の操作を行うことができます。</p>
                    <ul class="list-disc list-inside ml-4 mb-3 space-y-1">
                        <li>カレンダーの取得:<code>CalendarApp.getDefaultCalendar()</code></li>
                        <li>すべてのカレンダーの取得: <code>CalendarApp.getAllCalendars()</code></li>
                        <li>予定の作成:<code>calendar.createEvent(title, startTime, endTime, options)</code></li>
                        <li>予定の取得:<code>calendar.getEvents(startTime, endTime)</code></li>
                        <li>予定の更新: <code>event.setTitle(title)</code>, <code>event.setTime(startTime, endTime)</code> など</li>
                        <li>予定の削除: <code>event.deleteEvent()</code></li>
                    </ul>
                    <p class="font-semibold">予定の作成例:</p>
                    <div class="code-block">
<button class="copy-button" onclick="copyCode(this)">コピー</button>
<pre><code class="language-javascript">function createEvent() {
  // カレンダーを取得
  const calendar = CalendarApp.getDefaultCalendar();
  // 予定を作成
  calendar.createEvent(
    '会議', // 予定のタイトル
    new Date('2023-12-24T09:00:00'), // 開始日時
    new Date('2023-12-24T10:00:00'), // 終了日時
    {location: '会議室A'} // オプション
  );
}</code></pre>
                    </div>
                </div>
            </section>

            <section id="advanced-topics" class="tab-content">
                <h2 class="section-title">発展的なトピック</h2>
                <p class="mb-4">GAS開発をさらに一歩進めるためのトピックを紹介します。TypeScriptの導入によるコード品質の向上、外部ライブラリの活用、HTMLダイアログによるインタラクションの強化など、より高度なアプリ開発に役立つ知識を深めましょう。</p>
                <div class="bg-pink-50 p-4 rounded-lg border border-pink-200 space-y-6">
                    <div>
                        <h3 class="sub-section-title">TypeScript の使用</h3>
                        <p class="mb-2">GASの開発にTypeScriptを使用することで、コードの保守性向上とエラー削減に繋がります。 TypeScriptはJavaScriptに静的型付けを追加した言語で、大規模なプロジェクトや長期的なメンテナンスが必要な場合に特に有効です。</p>
                        <p class="font-semibold">TypeScriptの開発環境設定手順:</p>
                        <ol class="list-decimal list-inside ml-4 space-y-1 text-sm">
                            <li><code>clasp</code> (Command Line Apps Script Projects) をインストールします。</li>
                            <li>ローカル環境にGASプロジェクトのフォルダを作成し、<code>clasp create</code> コマンドでGASプロジェクトを作成します。</li>
                            <li><code>npm init -y</code> コマンドで<code>package.json</code>を作成し、 <code>npm install --save-dev typescript @types/google-apps-script</code> コマンドでTypeScriptとGASの型定義ファイルをインストールします。</li>
                            <li><code>tsconfig.json</code>を作成し、TypeScriptのコンパイルオプションを設定します。</li>
                            <li><code>.ts</code> 拡張子のTypeScriptファイルを作成し、GASのコードを記述します。</li>
                            <li><code>clasp push</code> コマンドでTypeScriptのコードをGASプロジェクトにアップロードします。</li>
                        </ol>
                    </div>
                    <div>
                        <h3 class="sub-section-title">外部ライブラリとフレームワークの活用: gas-db</h3>
                        <p class="mb-2"><code>gas-db</code> は、Googleスプレッドシートをデータベースのように扱うためのライブラリです。 スプレッドシートのデータを簡単に取得、更新、削除することができ、GASアプリケーションでのデータ管理を簡素化します。</p>
                         <p class="font-semibold">gas-dbの使用方法:</p>
                        <ol class="list-decimal list-inside ml-4 space-y-1 text-sm">
                            <li><code>gas-db</code> ライブラリをGASプロジェクトにインストールします。</li>
                            <li><code>gas-db</code> を使用してスプレッドシートのデータにアクセスするためのコードを記述します。</li>
                        </ol>
                    </div>
                    <div>
                        <h3 class="sub-section-title">HTMLダイアログの利用</h3>
                        <p class="mb-2">HTMLダイアログは、ユーザーからの入力を受け取ったり、確認メッセージを表示したりする際に役立ちます。 GASでは、 <code>HtmlService.createHtmlOutputFromFile</code> を使用してHTMLダイアログを作成し、表示することができます。</p>
                        <p class="font-semibold">HTMLダイアログの作成例:</p>
                        <div class="code-block">
<button class="copy-button" onclick="copyCode(this)">コピー</button>
<pre><code class="language-javascript">function showDialog() {
  const html = HtmlService.createHtmlOutputFromFile('dialog.html');
  SpreadsheetApp.getUi().showModalDialog(html, 'ダイアログのタイトル');
}</code></pre>
                        </div>
                    </div>
                </div>
            </section>

            <section id="sample-code" class="tab-content">
                <h2 class="section-title">サンプルコード</h2>
                <p class="mb-4">GASを使った様々な連携アプリケーションの具体的なサンプルコードを紹介します。これらのコードは、実際に手を動かして試すことで、GASの多様な可能性を体験するのに役立ちます。各サンプルはHTMLとGASのコードを含んでいます。</p>
                <div class="flex flex-col md:flex-row gap-4">
                    <div class="md:w-1/4">
                        <div id="sample-code-nav" class="space-y-2">
                            <button class="w-full text-left p-3 rounded-md bg-gray-100 hover:bg-gray-200 focus:bg-blue-500 focus:text-white transition-colors" onclick="showSampleCode('sample1')">サンプル1: シンプル登録</button>
                            <button class="w-full text-left p-3 rounded-md bg-gray-100 hover:bg-gray-200 focus:bg-blue-500 focus:text-white transition-colors" onclick="showSampleCode('sample2')">サンプル2: スプレッドシート連携</button>
                            <button class="w-full text-left p-3 rounded-md bg-gray-100 hover:bg-gray-200 focus:bg-blue-500 focus:text-white transition-colors" onclick="showSampleCode('sample3')">サンプル3: ドキュメント連携</button>
                            <button class="w-full text-left p-3 rounded-md bg-gray-100 hover:bg-gray-200 focus:bg-blue-500 focus:text-white transition-colors" onclick="showSampleCode('sample4')">サンプル4: サイト連携</button>
                            <button class="w-full text-left p-3 rounded-md bg-gray-100 hover:bg-gray-200 focus:bg-blue-500 focus:text-white transition-colors" onclick="showSampleCode('sample5')">サンプル5: フォーム連携</button>
                        </div>
                    </div>
                    <div class="md:w-3/4 bg-gray-50 p-4 rounded-lg border border-gray-200">
                        <div id="sample-code-content">
                            <p>左のメニューからサンプルを選択してください。</p>
                        </div>
                    </div>
                </div>
            </section>

            <section id="conclusion" class="tab-content">
                <h2 class="section-title">まとめ</h2>
                <p class="mb-4">本ガイドの学習内容を振り返り、GASを使ったアプリケーション開発のさらなるステップについて考えます。ここで得た知識とスキルを活かして、あなた自身のプロジェクトに挑戦してみましょう。</p>
                <div class="bg-purple-50 p-4 rounded-lg border border-purple-200">
                    <p class="mb-3">本ガイドでは、GASを使ったモダンなUIのスケジュール管理アプリ開発ハンズオンを通して、GASの基礎知識からモダンなUI構築、そしてGAS APIを使ったGoogleサービスとの連携までを学びました。ハンズオン形式で進めることで、実際に手を動かしながらGAS開発のスキルを習得することができたかと思います。</p>
                    <p class="mb-3">ハンズオンでは、HTML、CSS、JavaScriptを駆使してモダンなUIを構築し、GASのCalendar APIを使ってGoogleカレンダーと連携する機能を実装しました。さらに、スプレッドシートをデータベースとして活用したり、外部ライブラリを導入したりすることで、GASの拡張性を体験することができました。</p>
                    <p>GASは、Google Workspaceと連携した様々なアプリケーションを開発できる強力なツールです。本ガイドで学んだ知識を活かして、ぜひ独自のGASアプリケーション開発に挑戦してみてください。</p>
                </div>
            </section>

            <section id="references" class="tab-content">
                <h2 class="section-title">参考資料</h2>
                <p class="mb-4">本ガイド作成にあたり参考にしたウェブサイトやドキュメントの一覧です。これらのリソースも活用して、GASに関する知識をさらに深めてください。</p>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
                    <div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
                        <h3 class="sub-section-title">参考にしたサイトのリスト</h3>
                        <ul class="link-list list-disc list-inside ml-4 space-y-1">
                            <li><a href="https:// Caputo.dev/gas-calendar-schedule" target="_blank" rel="noopener noreferrer">GASでGoogleカレンダーに予定を登録する方法【GAS入門】</a></li>
                            <li><a href="https://qiita.com/soundTricker/items/a508333569431141a8f2" target="_blank" rel="noopener noreferrer">Google Apps Script で スプレッドシートを DB 的に扱うライブラリを作った</a></li>
                            <li><a href="https://blog.wadackel.me/2019/01/modernize-google-apps-script-development-environment/" target="_blank" rel="noopener noreferrer">Google Apps Script の開発環境をモダンにする</a></li>
                            <li><a href="https://tonari-it.com/gas-dialog-form-user-input/" target="_blank" rel="noopener noreferrer">GAS でダイアログにフォームを設置してユーザーからの入力を受け取る</a></li>
                            <li><a href="https://www.pc-koubou.jp/magazine/72113" target="_blank" rel="noopener noreferrer">非エンジニアがGASに入門するならこれ!スプレッドシートを操作して業務効率化</a></li>
                            <li><a href="https://auto-worker.com/blog/?p=5585" target="_blank" rel="noopener noreferrer">Googleカレンダーの予定をLINEで通知する方法【GAS】</a></li>
                            <li><a href="https://tech-lab.sios.jp/archives/45556" target="_blank" rel="noopener noreferrer">【GAS】Google Calendar APIを使って予定の登録・取得をしてみる</a></li>
                            <li><a href="https://zenn.dev/tokku5552/books/flutter-web-liff-scheduler" target="_blank" rel="noopener noreferrer">Flutter Web × LIFF で作る!GAS で LINE Bot 連携したスケジュール管理アプリ</a></li>
                            <li><a href="https://feynman.co.jp/it-tool-takumi/google-calendar-tasks/" target="_blank" rel="noopener noreferrer">【GAS】スプレッドシートのタスクをGoogleカレンダーに自動登録する方法</a></li>
                            <li><a href="https://yamady-works.com/googlecalendar-gas/54/" target="_blank" rel="noopener noreferrer">【GAS】Googleカレンダーとスプレッドシートを連携する方法を解説</a></li>
                        </ul>
                    </div>
                    <div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
                        <h3 class="sub-section-title">引用文献</h3>
                        <ol class="link-list list-decimal list-inside ml-4 space-y-1">
                            <li><a href="https://weseek.co.jp/tech/1494/" target="_blank" rel="noopener noreferrer">あなたもできる!GASで勤怠入力Slack App構築</a></li>
                            <li><a href="https://kenchan0130.github.io/post/2018-12-03-1" target="_blank" rel="noopener noreferrer">Google Apps Scriptのモダンな開発環境を求めて - kenchan0130 blog</a></li>
                            <li><a href="https://zenn.dev/shunta_furukawa/articles/d99b2635679dfd" target="_blank" rel="noopener noreferrer">Google Apps Script + gas-db + HTML を使った TODO アプリ - Zenn</a></li>
                            <li><a href="https://zenn.dev/zaki_yama/articles/gas-form-dialog" target="_blank" rel="noopener noreferrer">GASでシンプルなフォームつきダイアログを実装する - Zenn</a></li>
                            <li><a href="https://www.seplus.jp/dokushuzemi/blog/2022/03/learn_gas4non_developers.html" target="_blank" rel="noopener noreferrer">3 時間で学ぶ Google Apps Script 入門 ~スプレッドシートと連携 ...</a></li>
                            <li><a href="https://feynman.co.jp/it-tool-takumi/google-calendar-line-api-schedule-notification/" target="_blank" rel="noopener noreferrer">GAS×LINEでGoogleカレンダー当日の予定を自動通知する方法 ...</a></li>
                            <li><a href="https://tech-lab.sios.jp/archives/45556" target="_blank" rel="noopener noreferrer">【サンプルコード付き】GASでGoogle Calendarを操作する方法を ...</a></li>
                            <li><a href="https://zenn.dev/tokku5552/books/flutter-web-liff-scheduler/viewer/9_gas" target="_blank" rel="noopener noreferrer">【コード解説:GAS】スケジュール一覧取得/スケジュールの作成API ...</a></li>
                            <li><a href="https://feynman.co.jp/it-tool-takumi/google-calendar-tasks/" target="_blank" rel="noopener noreferrer">GAS活用!スプレッドシートのタスク表をGoogleカレンダーに ...</a></li>
                            <li><a href="https://yamady-works.com/googlecalendar-gas/54/" target="_blank" rel="noopener noreferrer">【GAS】Googleカレンダー×スプレッドシートでスケジュール管理 ...</a></li>
                        </ol>
                    </div>
                </div>
            </section>
        </main>
    </div>

<script>
    const tabContents = {};
    const sampleCodeDetails = {
        sample1: {
            title: "サンプルコード1:シンプルなスケジュール登録アプリ",
            description: "このサンプルコードでは、HTML Serviceを使ってシンプルなUIを作成し、GASのCalendar APIを使ってGoogleカレンダーに予定を登録するアプリを作成します。",
            html: `&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;base target="_top"&gt;
  &lt;style&gt;
    body { font-family: sans-serif; margin: 20px; }
    label { display: block; margin-top: 10px; }
    input[type="text"], input[type="datetime-local"] { width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
    button { background-color: #4CAF50; color: white; padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; margin-top: 20px; }
    button:hover { background-color: #45a049; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;スケジュール登録&lt;/h1&gt;
  &lt;form id="scheduleForm"&gt;
    &lt;label for="title"&gt;タイトル:&lt;/label&gt;
    &lt;input type="text" id="title" name="title" required&gt;
    &lt;label for="startDate"&gt;開始日時:&lt;/label&gt;
    &lt;input type="datetime-local" id="startDate" name="startDate" required&gt;
    &lt;label for="endDate"&gt;終了日時:&lt;/label&gt;
    &lt;input type="datetime-local" id="endDate" name="endDate" required&gt;
    &lt;button type="submit"&gt;登録&lt;/button&gt;
  &lt;/form&gt;
  &lt;script&gt;
    document.getElementById('scheduleForm').addEventListener('submit', function(e) {
      e.preventDefault();
      const title = document.getElementById('title').value;
      const startDate = document.getElementById('startDate').value;
      const endDate = document.getElementById('endDate').value;
      google.script.run
        .withSuccessHandler(() => alert('スケジュールを登録しました!'))
        .withFailureHandler((err) => alert('登録に失敗しました: ' + err.message))
        .createSchedule(title, startDate, endDate);
      // フォームをリセット(任意)
      // document.getElementById('scheduleForm').reset();
    });
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;`,
            gas: `function doGet() {
  return HtmlService.createHtmlOutputFromFile('index').setTitle('スケジュール登録アプリ');
}

function createSchedule(title, startDate, endDate) {
  if (!title || !startDate || !endDate) {
    throw new Error('タイトル、開始日時、終了日時は必須です。');
  }
  try {
    const calendar = CalendarApp.getDefaultCalendar();
    calendar.createEvent(title, new Date(startDate), new Date(endDate));
    return "成功"; // 成功メッセージを返す(任意)
  } catch (e) {
    console.error("Error in createSchedule: " + e.toString());
    throw new Error('カレンダーへのイベント作成中にエラーが発生しました: ' + e.message);
  }
}`
        },
        sample2: {
            title: "サンプルコード2:スプレッドシートと連携したスケジュール管理アプリ",
            description: "このサンプルコードでは、スプレッドシートに保存されているスケジュールデータをGASで取得し、HTML Serviceを使ってカレンダー形式で表示するアプリを作成します。",
            html: `&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;base target="_top"&gt;
  &lt;style&gt;
    body { font-family: sans-serif; margin: 20px; }
    #calendar { border: 1px solid #ddd; padding: 10px; min-height: 200px; background-color: #f9f9f9; border-radius: 4px; }
    .event { border-bottom: 1px solid #eee; padding: 5px 0; }
    .event:last-child { border-bottom: none; }
    .event-title { font-weight: bold; }
    .event-date { font-size: 0.9em; color: #555; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;スケジュール管理 (スプレッドシート連携)&lt;/h1&gt;
  &lt;div id="calendar"&gt;読み込み中...&lt;/div&gt;
  &lt;script&gt;
    function displaySchedules(schedules) {
      const calendarDiv = document.getElementById('calendar');
      calendarDiv.innerHTML = ''; // クリア
      if (schedules && schedules.length > 0) {
        schedules.forEach(function(schedule) {
          const eventDiv = document.createElement('div');
          eventDiv.classList.add('event');
          
          const titleP = document.createElement('p');
          titleP.classList.add('event-title');
          titleP.textContent = schedule.title;
          
          const dateP = document.createElement('p');
          dateP.classList.add('event-date');
          dateP.textContent = '開始: ' + new Date(schedule.startDate).toLocaleString() + ' - 終了: ' + new Date(schedule.endDate).toLocaleString();
          
          eventDiv.appendChild(titleP);
          eventDiv.appendChild(dateP);
          calendarDiv.appendChild(eventDiv);
        });
      } else {
        calendarDiv.textContent = 'スケジュールはありません。';
      }
    }
    
    google.script.run
      .withSuccessHandler(displaySchedules)
      .withFailureHandler(err => {
         document.getElementById('calendar').textContent = 'スケジュールの読み込みに失敗しました: ' + err.message;
      })
      .getSchedulesFromSheet();
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;`,
            gas: `// スプレッドシートの準備:
// 1. 新しいGoogleスプレッドシートを作成します。
// 2. 1行目にヘッダーとして「タイトル」「開始日時」「終了日時」と入力します。
// 3. 2行目以降にサンプルデータを入力します(開始日時と終了日時は "2024/07/15 10:00:00" のような形式で)。
// 4. スプレッドシート名を 'スケジュール' に変更します(または下記の SPREADSHEET_NAME を実際のシート名に)。

const SPREADSHEET_NAME = 'スケジュール'; // ここを実際のスプレッドシート名に
const SHEET_NAME = 'シート1'; // ここを実際のシート名に (通常は 'シート1' または 'Sheet1')

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index').setTitle('スプレッドシート連携スケジュール');
}

function getSchedulesFromSheet() {
  try {
    const ss = SpreadsheetApp.getActiveSpreadsheet(); // または SpreadsheetApp.openById("SPREADSHEET_ID")
    const sheet = ss.getSheetByName(SHEET_NAME);
    if (!sheet) {
        throw new Error("シート '" + SHEET_NAME + "' が見つかりません。");
    }
    
    const data = sheet.getDataRange().getValues();
    const schedules = [];
    
    // ヘッダー行をスキップ (i=1 から開始)
    for (let i = 1; i < data.length; i++) {
      if (data[i][0] && data[i][1] && data[i][2]) { // タイトル、開始、終了が空でないことを確認
        schedules.push({
          title: data[i][0],       // A列: タイトル
          startDate: data[i][1],   // B列: 開始日時
          endDate: data[i][2]      // C列: 終了日時
        });
      }
    }
    return schedules;
  } catch (e) {
    console.error("Error in getSchedulesFromSheet: " + e.toString());
    throw new Error('スプレッドシートからのデータ取得中にエラーが発生しました: ' + e.message);
  }
}`
        },
        sample3: {
            title: "サンプルコード3:ドキュメントと連携した議事録作成アプリ",
            description: "このサンプルコードでは、Googleカレンダーの予定を取得し、議事録のテンプレートを元にGoogleドキュメントを自動生成するアプリを作成します。(GAS側のみの実行を想定)",
            html: `&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;base target="_top"&gt;
  &lt;style&gt;
    body { font-family: sans-serif; margin: 20px; display: flex; flex-direction: column; align-items: center; }
    button { background-color: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 5px; font-size: 16px; cursor: pointer; }
    button:hover { background-color: #0056b3; }
    #status { margin-top: 20px; font-style: italic; color: #555; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;議事録自動作成&lt;/h1&gt;
  &lt;p&gt;今日の直近の予定から議事録を作成します。(テンプレートIDをGASコード内で設定してください)&lt;/p&gt;
  &lt;button onclick="runCreateMinutes()"&gt;議事録を作成&lt;/button&gt;
  &lt;div id="status"&gt;&lt;/div&gt;
  &lt;script&gt;
    function runCreateMinutes() {
      const statusDiv = document.getElementById('status');
      statusDiv.textContent = '議事録作成中...';
      google.script.run
        .withSuccessHandler((result) => {
          statusDiv.textContent = result;
        })
        .withFailureHandler((err) => {
          statusDiv.textContent = 'エラー: ' + err.message;
        })
        .createMinutesFromCalendar();
    }
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;`,
            gas: `// 準備: 
// 1. Googleドキュメントで議事録のテンプレートを作成します。
//    例: タイトルに {イベントタイトル}、参加者に {参加者} のようなプレースホルダーを挿入。
// 2. テンプレートドキュメントのIDをコピーします (URLの d/ と /edit の間の文字列)。
// 3. 下記の TEMPLATE_DOC_ID を実際のIDに置き換えます。

const TEMPLATE_DOC_ID = 'YOUR_TEMPLATE_DOCUMENT_ID_HERE'; // ここにテンプレートドキュメントのIDを入力

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index').setTitle('議事録作成');
}

function createMinutesFromCalendar() {
  try {
    if (TEMPLATE_DOC_ID === 'YOUR_TEMPLATE_DOCUMENT_ID_HERE') {
        throw new Error('テンプレートドキュメントIDが設定されていません。GASコードを編集してください。');
    }
    const calendar = CalendarApp.getDefaultCalendar();
    // 例として、今日から7日後までの予定を取得
    const now = new Date();
    const sevenDaysLater = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000));
    const events = calendar.getEvents(now, sevenDaysLater);

    if (events.length === 0) {
      return '対象期間に予定が見つかりませんでした。';
    }

    const templateDoc = DriveApp.getFileById(TEMPLATE_DOC_ID);
    let createdCount = 0;

    events.forEach(function(event) {
      const eventTitle = event.getTitle();
      const eventDate = Utilities.formatDate(event.getStartTime(), Session.getScriptTimeZone(), 'yyyy/MM/dd');
      const newDocName = eventTitle + ' 議事録 (' + eventDate + ')';
      
      // 同じ名前のファイルが既に存在するかチェック(任意)
      const existingFiles = DriveApp.getFilesByName(newDocName);
      if (existingFiles.hasNext()) {
        console.log("議事録 '" + newDocName + "' は既に存在します。スキップします。");
        return; // 次のイベントへ
      }

      const newDocFile = templateDoc.makeCopy(newDocName);
      const newDoc = DocumentApp.openById(newDocFile.getId());
      const body = newDoc.getBody();
      
      // テンプレート内のプレースホルダーを置換
      body.replaceText('{イベントタイトル}', eventTitle);
      body.replaceText('{開催日時}', event.getStartTime().toLocaleString());
      body.replaceText('{場所}', event.getLocation() || '未定');
      // body.replaceText('{参加者}', event.getGuestList().map(g => g.getEmail()).join(', ') || 'なし');
      
      newDoc.saveAndClose();
      createdCount++;
      console.log("議事録 '" + newDocName + "' を作成しました。ID: " + newDocFile.getId());
    });
    
    return createdCount + "件の議事録を作成しました。";

  } catch (e) {
    console.error("Error in createMinutesFromCalendar: " + e.toString());
    throw new Error('議事録作成中にエラーが発生しました: ' + e.message);
  }
}`
        },
        sample4: {
            title: "サンプルコード4:サイトと連携した情報表示アプリ",
            description: "このサンプルコードでは、外部サイト(例: JSONPlaceholder)から情報を取得し、HTML Serviceを使ってWebページに表示するアプリを作成します。",
            html: `&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;base target="_top"&gt;
  &lt;style&gt;
    body { font-family: sans-serif; margin: 20px; }
    #information { border: 1px solid #ddd; padding: 10px; background-color: #f9f9f9; border-radius: 4px; }
    .post { margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; }
    .post:last-child { border-bottom: none; }
    .post-title { font-size: 1.2em; font-weight: bold; color: #337ab7; }
    .post-body { margin-top: 5px; font-size: 0.9em; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;外部情報表示 (JSONPlaceholderから投稿を取得)&lt;/h1&gt;
  &lt;div id="information"&gt;読み込み中...&lt;/div&gt;
  &lt;script&gt;
    function displayInformation(posts) {
      const infoDiv = document.getElementById('information');
      infoDiv.innerHTML = ''; // クリア
      if (posts && posts.length > 0) {
        posts.forEach(function(post) {
          const postDiv = document.createElement('div');
          postDiv.classList.add('post');
          
          const titleH3 = document.createElement('h3');
          titleH3.classList.add('post-title');
          titleH3.textContent = post.title;
          
          const bodyP = document.createElement('p');
          bodyP.classList.add('post-body');
          bodyP.textContent = post.body;
          
          postDiv.appendChild(titleH3);
          postDiv.appendChild(bodyP);
          infoDiv.appendChild(postDiv);
        });
      } else {
        infoDiv.textContent = '情報を取得できませんでした。';
      }
    }

    google.script.run
      .withSuccessHandler(displayInformation)
      .withFailureHandler(err => {
        document.getElementById('information').textContent = '情報取得エラー: ' + err.message;
      })
      .fetchExternalInformation();
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;`,
            gas: `function doGet() {
  return HtmlService.createHtmlOutputFromFile('index').setTitle('外部情報表示');
}

function fetchExternalInformation() {
  try {
    // JSONPlaceholderから投稿データを取得する例
    const url = 'https://jsonplaceholder.typicode.com/posts?_limit=5'; // 最初の5件の投稿
    const response = UrlFetchApp.fetch(url);
    const jsonResponse = response.getContentText();
    const data = JSON.parse(jsonResponse);
    
    // 必要な情報だけを抽出・整形(ここではそのまま返すが、実際は整形することが多い)
    // 例: return data.map(post => ({ title: post.title, summary: post.body.substring(0,100) + '...' }));
    return data;

  } catch (e) {
    console.error("Error in fetchExternalInformation: " + e.toString());
    throw new Error('外部情報の取得中にエラーが発生しました: ' + e.message);
  }
}`
        },
        sample5: {
            title: "サンプルコード5:フォームと連携したアンケート集計アプリ",
            description: "このサンプルコードでは、Googleフォームの回答を取得し、HTML Serviceを使って集計結果(回答数のみ)を表示するアプリを作成します。",
            html: `&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
  &lt;base target="_top"&gt;
  &lt;style&gt;
    body { font-family: sans-serif; margin: 20px; }
    #results { border: 1px solid #ddd; padding: 20px; background-color: #f0f9ff; border-radius: 4px; text-align: center; }
    #results h2 { margin-top: 0; color: #005a9e; }
    #results p { font-size: 1.5em; font-weight: bold; color: #007bff; margin: 10px 0 0 0; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;アンケート集計結果&lt;/h1&gt;
  &lt;p&gt;(GASコード内で連携するフォームのIDを設定してください)&lt;/p&gt;
  &lt;div id="results"&gt;読み込み中...&lt;/div&gt;
  &lt;script&gt;
    function displayResults(summary) {
      const resultsDiv = document.getElementById('results');
      resultsDiv.innerHTML = ''; // クリア
      
      const title = document.createElement('h2');
      title.textContent = summary.formTitle || 'アンケート結果';
      resultsDiv.appendChild(title);

      const countP = document.createElement('p');
      countP.textContent = '総回答数: ' + summary.responseCount;
      resultsDiv.appendChild(countP);

      // ここでさらに詳細な集計結果を表示するロジックを追加可能
      // 例: 各質問の回答の割合など
      if(summary.questionSummaries && summary.questionSummaries.length > 0){
          summary.questionSummaries.forEach(qSum => {
              const qDiv = document.createElement('div');
              qDiv.style.marginTop = '15px';
              qDiv.innerHTML = \`<strong>\${qSum.questionTitle}:</strong><br>\`;
              if(qSum.optionsCount){
                  for(const option in qSum.optionsCount){
                      qDiv.innerHTML += \`&nbsp;&nbsp;\${option}: \${qSum.optionsCount[option]}票<br>\`;
                  }
              }
              resultsDiv.appendChild(qDiv);
          });
      }


    }

    google.script.run
      .withSuccessHandler(displayResults)
      .withFailureHandler(err => {
        document.getElementById('results').innerHTML = '<h2>集計エラー</h2><p>' + err.message + '</p>';
      })
      .getFormResultsSummary();
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;`,
            gas: `// 準備:
// 1. Googleフォームでアンケートを作成します。
// 2. フォームのIDをコピーします (URLの /d/ と /edit の間の文字列、またはフォーム編集画面のURLから)。
// 3. 下記の FORM_ID を実際のフォームIDに置き換えます。
//    または、スクリプトがフォームにバインドされている場合は FormApp.getActiveForm() を使用します。

const FORM_ID = 'YOUR_GOOGLE_FORM_ID_HERE'; // ここに連携するGoogleフォームのIDを入力

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index').setTitle('アンケート集計');
}

function getFormResultsSummary() {
  try {
    let form;
    if (FORM_ID && FORM_ID !== 'YOUR_GOOGLE_FORM_ID_HERE') {
      form = FormApp.openById(FORM_ID);
    } else {
      // スクリプトがフォームにバインドされている場合
      // form = FormApp.getActiveForm(); 
      // バインドされていない場合はエラー
      throw new Error('GoogleフォームのIDが設定されていません。GASコードを編集するか、スクリプトをフォームにバインドしてください。');
    }
    
    const formTitle = form.getTitle();
    const responses = form.getResponses();
    const responseCount = responses.length;

    // 簡単な質問ごとの集計 (選択式の場合)
    const questionSummaries = [];
    const items = form.getItems();
    items.forEach(item => {
        const questionTitle = item.getTitle();
        const itemResponses = [];
        responses.forEach(response => {
            const itemResponse = response.getResponseForItem(item);
            if (itemResponse) {
                itemResponses.push(itemResponse.getResponse());
            }
        });

        if (itemResponses.length > 0) {
            // 選択肢のカウント (単一選択、複数選択、プルダウンなど)
            // より複雑な質問タイプには追加の処理が必要
            if (item.getType() === FormApp.ItemType.MULTIPLE_CHOICE || 
                item.getType() === FormApp.ItemType.LIST ||
                item.getType() === FormApp.ItemType.CHECKBOX) {
                
                const optionsCount = {};
                itemResponses.forEach(resp => {
                    // チェックボックスは配列で回答が返る
                    const answers = Array.isArray(resp) ? resp : [resp];
                    answers.forEach(ans => {
                        optionsCount[ans] = (optionsCount[ans] || 0) + 1;
                    });
                });
                questionSummaries.push({ questionTitle: questionTitle, optionsCount: optionsCount });
            } else {
                 questionSummaries.push({ questionTitle: questionTitle, responseCount: itemResponses.length });
            }
        }
    });

    return {
      formTitle: formTitle,
      responseCount: responseCount,
      questionSummaries: questionSummaries
    };

  } catch (e) {
    console.error("Error in getFormResultsSummary: " + e.toString());
    throw new Error('フォーム結果の集計中にエラーが発生しました: ' + e.message);
  }
}`
        }
    };


    function showTab(tabId) {
        const mainContent = document.getElementById('main-content');
        const sections = mainContent.getElementsByTagName('section');
        for (let i = 0; i < sections.length; i++) {
            sections[i].classList.remove('active');
        }
        document.getElementById(tabId).classList.add('active');

        const buttons = document.getElementById('tab-navigation').getElementsByTagName('button');
        for (let i = 0; i < buttons.length; i++) {
            buttons[i].classList.remove('active');
        }
        event.currentTarget.classList.add('active');
         // Scroll to top of main content for better UX on tab change
        mainContent.scrollTop = 0; 
        window.scrollTo({ top: mainContent.offsetTop - 20, behavior: 'smooth' });
    }
    
    function showSampleCode(sampleId) {
        const contentDiv = document.getElementById('sample-code-content');
        const sample = sampleCodeDetails[sampleId];

        if (sample) {
            contentDiv.innerHTML = `
                <h3 class="text-xl font-semibold mb-2 text-gray-700">${sample.title}</h3>
                <p class="text-sm text-gray-600 mb-4">${sample.description}</p>
                <div class="mb-6">
                    <h4 class="text-lg font-medium mb-1 text-gray-600">HTML (index.html)</h4>
                    <div class="code-block">
                        <button class="copy-button" onclick="copyCode(this)">コピー</button>
                        <pre><code class="language-html">${sample.html.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</code></pre>
                    </div>
                </div>
                <div>
                    <h4 class="text-lg font-medium mb-1 text-gray-600">GAS (code.gs)</h4>
                     <div class="code-block">
                        <button class="copy-button" onclick="copyCode(this)">コピー</button>
                        <pre><code class="language-javascript">${sample.gas.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</code></pre>
                    </div>
                </div>
            `;
        } else {
            contentDiv.innerHTML = '<p>サンプルコードが見つかりません。</p>';
        }

        const sampleNavButtons = document.getElementById('sample-code-nav').getElementsByTagName('button');
        for (let i = 0; i < sampleNavButtons.length; i++) {
            sampleNavButtons[i].classList.remove('focus:bg-blue-500', 'focus:text-white'); // Remove focus style from others
        }
         // Add focus style to current button, ensure event is passed if called directly
        if(event && event.currentTarget){
             event.currentTarget.classList.add('focus:bg-blue-500', 'focus:text-white');
        } else {
            // If called programmatically, find the button
            for (let i = 0; i < sampleNavButtons.length; i++) {
                if(sampleNavButtons[i].getAttribute('onclick').includes(sampleId)){
                    sampleNavButtons[i].classList.add('focus:bg-blue-500', 'focus:text-white');
                    break;
                }
            }
        }
    }

    function copyCode(buttonElement) {
        const preElement = buttonElement.nextElementSibling;
        const code = preElement.textContent;
        navigator.clipboard.writeText(code).then(() => {
            buttonElement.textContent = 'コピー完了!';
            setTimeout(() => {
                buttonElement.textContent = 'コピー';
            }, 2000);
        }).catch(err => {
            console.error('コピーに失敗しました', err);
            buttonElement.textContent = '失敗';
             setTimeout(() => {
                buttonElement.textContent = 'コピー';
            }, 2000);
        });
    }

    // 初期表示
    window.onload = () => {
        showTab('overview'); // 最初に概要タブを表示
        // デフォルトで最初のサンプルコードを表示(任意)
        // showSampleCode('sample1');
        // const firstSampleButton = document.querySelector('#sample-code-nav button');
        // if(firstSampleButton) firstSampleButton.classList.add('focus:bg-blue-500', 'focus:text-white');

         // Activate the first sample code button style if exists
        const firstSampleNavButton = document.querySelector('#sample-code-nav button');
        if (firstSampleNavButton) {
            showSampleCode(firstSampleNavButton.getAttribute('onclick').match(/'([^']+)'/)[1]); // Extract sampleId
            firstSampleNavButton.classList.add('focus:bg-blue-500', 'focus:text-white');
        } else {
             document.getElementById('sample-code-content').innerHTML = '<p>左のメニューからサンプルを選択してください。</p>';
        }
    };
</script>
</body>
</html>

コメント

タイトルとURLをコピーしました