SharePoint をホストとする Project Server アドインを作成する

Project Online用に作成できる 3 種類のアプリ (自動ホスト、プロバイダーホスト、および SharePoint ホスト) のうち、SharePoint でホストされるアプリは、最も簡単に作成および展開できます。 SharePoint ホスト型アプリは OAuth 認証を必要とせず、Azure を使用することも、プロバイダーでホストされるリソースのローカル サイトのメンテナンスも必要としません。 Visual Studio の App for SharePoint 2013 テンプレートは、Office ストアで発行および販売したり、SharePoint 上のプライベート アプリ カタログに展開したりできるアプリを開発するための便利なフレームワークです。

Project では、状態は、チーム メンバーがProject Web Appの [タスク] ページを使用して、割り当てられたタスクの状態 (タスクの作業に費やされた毎日の作業時間など) を送信できるプロセスです。 割り当て所有者 (通常はプロジェクト マネージャー) は、状態を承認または拒否できます。 状態が承認されると、Project によってスケジュールが再計算されます。 QuickStatus アプリには、割り当てられたタスクが表示されます。ここで、ユーザーは、承認のために選択した割り当ての完了率をすばやく更新し、状態を送信できます。 Project Web Appの [タスク] ページにははるかに多くの機能がありますが、QuickStatus アプリは簡略化されたインターフェイスを提供する例です。

QuickStatus アプリは開発者向けのサンプルです。運用環境での使用を目的としたものではありません。 主な目的は、完全に機能する状態アプリを作成するのではなく、Project Online用のアプリ開発の例を示することです。 状態を改善する方法については、「 次の手順」の推奨事項を参照してください。

状態に関する一般的な情報については、「 タスクの進行状況」を参照してください。 SharePoint および Project Server 用アドインの開発の詳細については、「 SharePoint アドイン」を参照してください。

Project Server 2013 用アプリを作成するための前提条件

Project Onlineまたは Project Server 2013 のオンプレミス インストールに展開できる比較的単純なアプリを開発するには、オンライン開発環境を提供する Napa を使用できます。 より複雑なアプリ、Project Web App リボンの変更、開発中のデバッグの容易な場合は、Visual Studio 2012 または Visual Studio 2013を使用できます。 たとえば、オンプレミスのインストールでは、Project Server データベースの変更に関する下書きデータテーブルを手動でチェックできます。 この記事では、Visual Studio でアプリ開発を行う方法について説明します。

Visual Studio を使用した Project Server アプリの開発には、次のものが必要です。

  • 使用するローカルの開発用コンピューターに最新のサービス パックと Windows 更新プログラムをインストールしてあることを確認します。 オペレーティング システムは、Windows 7、Windows 8、Windows Server 2008、Windows Server 2012 のいずれでもかまいません。

  • SharePoint Server 2013 と Project Server 2013 がインストールされているコンピューターが必要です。このコンピューターは、アプリの分離とアプリのサイドローディング用に構成されています。 サイドローディングを使用すると、Visual Studio はデバッグのためにアプリを一時的にインストールできます。 SharePoint と Project Server のオンプレミス インストールを使用できます。 詳細については、「 SharePoint 用アプリのオンプレミス開発環境を設定する」を参照してください。

    注:

    オンプレミスのインストールの場合は、会社のアプリ カタログを作成する 前に 、分離されたアプリ ドメインを構成します。

  • 開発用コンピューターには、Office Developer Tools for Visual Studio 2012 がインストールされているリモート コンピューターを指定できます。 最新バージョンがインストールされていることを確認します。「Office および SharePoint ダウンロード用アプリ」「ツール」セクションを参照してください。

  • 開発とテストに使用するProject Web App インスタンスにブラウザーでアクセスできることを確認します。

オンライン ツールの使用については、「 Microsoft 365 で SharePoint アドインの開発環境を設定する」を参照してください。 オンライン ツールを使用する Project Server 用の単純なアプリを構築するチュートリアルについては、EPMSource ブログ シリーズ「 初めての Project Server アプリのビルド」を参照してください。

Visual Studio を使用して Project Server アプリを作成する

Office Developer Tools for Visual Studio 2012 には、Project Server 2013 で使用できる SharePoint アプリ用のテンプレートが含まれています。 アプリ ソリューションを作成すると、ソリューションにはカスタム コード用の次のファイルが含まれます。

  • AppManifest.xml には、アプリ タイトル、アクセス許可要求スコープ、およびその他のプロパティの設定が含まれています。 手順 1 には、マニフェスト Designerを使用してプロパティを設定する手順が含まれています。

  • Pages フォルダー内のDefault.aspxは、アプリのメイン ページです。 手順 2 では、 QuickStatus アプリの HTML5 コンテンツを追加する方法を示します。

  • Scripts フォルダー内のApp.jsは、カスタム JavaScript コードのプライマリ ファイルです。 手順 3 では、 QuickStatus アプリの JavaScript コードについて説明します。

    jQuery ベースのグリッドや日付ピッカーなどの商用コントロールを追加する場合は、Default.aspx ファイル内の追加の JavaScript ファイルへの参照を追加できます。

  • [ コンテンツ] フォルダーのApp.cssは、カスタム CSS3 スタイルのプライマリ ファイルです。 手順 2 と手順 3 には、 QuickStatus アプリのカスケード スタイル シート (CSS) スタイルに関する情報が含まれています。 Default.aspx ファイル内の追加の CSS ファイルへの参照を追加できます。

  • Images フォルダーのAppIcon.pngは、アプリが Office ストアまたはアプリ カタログに表示する 96 x 96 アイコンです。

Project Web App リボンを変更するには、リボンのカスタム アクションを追加します。 [QuickStatus アプリのコード例] セクションには、変更されたDefault.aspx、App.js、App.css、Elements.xml、AppManifest.xml ファイルの完全なコードが含まれています。

手順 1. Visual Studio でアプリ プロジェクトを作成するには

  1. 管理者として Visual Studio 2012 を実行し、[スタート] ページで [新しいプロジェクト ] を選択します。

  2. [ 新しいプロジェクト ] ダイアログ ボックスで、[ テンプレート]、[ Visual C#]、[ Office/SharePoint ] の各ノードを展開し、[ アプリ] を選択します。 中央のウィンドウの上部にあるターゲット フレームワーク のドロップダウン リストで既定の.NET Framework 4.5 を使用し、[App for SharePoint 2013] を選択します (図 1 を参照)。

  3. [ 名前 ] フィールドに「QuickStatus」と入力し、アプリを保存する場所を参照して、[OK] を選択します

    図 1. Visual Studio での Project Server アプリの作成

    Visual Studio での Project Server アプリの作成 Visual Studio

  4. [ SharePoint 用新しいアプリ ] ダイアログ ボックスで、次の 3 つのフィールドに入力します。

    • 上部のテキスト ボックスに、アプリをProject Web Appに表示する名前を入力します。 たとえば、「クイック ステータス更新」と入力します。

    • デバッグに使用するサイトの場合は、Project Web App インスタンスの URL を入力します。 たとえば、「(ServerNameProjectServerName を独自の値に置き換える)」と入力https://ServerName/ProjectServerNameし、[検証] を選択します。 すべてが正常に完了すると、Visual Studio に [接続が成功しました] と表示されます。 エラー メッセージが表示された場合は、Project Web App URL が正しいこと、および Project Server コンピューターがアプリの分離とアプリのサイドローディング用に構成されていることを確認します。 詳細については、「 Project Server 2013 用アプリを作成するための前提条件 」セクションを参照してください。

    • [ SharePoint 用にアプリをホストする方法 ] ドロップダウン リストで、[ SharePoint ホスト型] を選択します。

    注意

    プロバイダーホスト型の既定のプロジェクトの種類を誤って選択した場合、Visual Studio はソリューションに QuickStatus プロジェクトと QuickStatusWeb プロジェクトという 2 つのプロジェクトを作成します。 2 つのプロジェクトが表示された場合は、そのソリューションを削除して、もう一度開始します。

  5. [ OK] を 選択して 、QuickStatus ソリューション、 QuickStatus プロジェクト、および既定のファイルを作成します。

  6. マニフェスト Designer ビューを開きます (たとえば、AppManifest.xml ファイルをダブルクリックします)。 [ 全般 ] タブの [ タイトル ] テキスト ボックスに、手順 4 で入力したアプリ名が表示されます。 [ アクセス許可 ] タブを選択して、アプリに対する次のアクセス許可要求を追加します (図 2 を参照)。

    • [ アクセス許可要求 ] リストの最初の行の [スコープ ] 列で、ドロップダウン リストの [状態 ] を選択します。 [ アクセス許可 ] 列で、[ SubmitStatus] を選択します。

    • [スコープ][複数のプロジェクト] で 、[アクセス許可] が [読み取り] の行を追加します。

    図 2. 状態管理アプリのアクセス許可の範囲を設定

    ステータス アプリのアクセス許可スコープの設定 ステータス アプリ

QuickStatus アプリを使用すると、Project Web App ユーザーは、複数のプロジェクトからそのユーザーの割り当てを読み取り、割り当ての完了率を変更し、更新プログラムを送信できます。 図 2 のドロップダウン リストに表示されているその他のアクセス許可要求スコープは、このアプリには必要ありません。 アクセス許可要求スコープは、アプリがユーザーに代わって要求するアクセス許可です。 ユーザーがProject Web Appでこれらのアクセス許可を持っていない場合、アプリは実行されません。 アプリには、他の SharePoint アクセス許可のスコープを含む複数のアクセス許可要求スコープを含めることができますが、アプリの機能に必要な最小限のスコープのみを持つ必要があります。 Project Server に関連するアクセス許可要求スコープを次に示します。

  • エンタープライズ リソース: 他のProject Web App ユーザーに関する情報を読み書きするためのリソース マネージャーのアクセス許可。

  • 複数のプロジェクト: ユーザーが要求したアクセス許可を持つ複数のプロジェクトに対する読み取りまたは書き込みを行います。

  • Project Server: アプリ ユーザーがProject Web Appの管理者権限を持っている必要があります。

  • レポート: Project Web Appの ProjectData OData サービスを読み取ります (Project Web Appのログオンアクセス許可のみが必要です)。

  • 単一プロジェクト: ユーザーが要求したアクセス許可を持つプロジェクトの読み取りまたは書き込みを行います。

  • 状態: 作業時間、達成率、新しい割り当てなど、割り当ての状態の更新を送信します。

  • ワークフロー: ユーザーが Project Server ワークフローを実行するアクセス許可を持っている場合、アプリはワークフローの管理者特権で実行されます。

Project Server 2013 のアクセス許可要求スコープの詳細については、Project 2013 の開発者向けの更新の「Projectapps」セクションと「SharePoint 2013 のアプリのアクセス許可」を参照してください。

QuickStatus アプリの HTML コンテンツの作成

HTML コンテンツのコーディングを開始する前に、QuickStatus アプリのユーザー インターフェイスとユーザー エクスペリエンスを設計します (図 3 は、完成したページの例を示しています)。 デザインには、HTML コードと対話する JavaScript 関数の概要を含めることもできます。 一般的な情報については、「 SharePoint 2013 のアプリの UX 設計」を参照してください。

図 3. QuickStatus アプリ ページの設計

QuickStatus アプリ ページのデザイン

アプリの上部に表示名が表示されます。これは、AppManifest.xml の Title 要素の値です。

既定では、ページは HTML5 を使用します。 QuickStatus アプリがページの本文に含めるメイン UI オブジェクトの標準 HTML 要素を次に示します。

  • フォーム要素には、他のすべての UI 要素が含まれています。

  • fieldset 要素は、割り当てのテーブルのコンテナーと罫線を作成します。子凡例要素は、コンテナーのラベルを提供します。

  • table 要素には、キャプションとテーブル ヘッダーのみが含まれます。 JavaScript 関数は、テーブルのキャプションを変更し、割り当ての行を追加します。

    注:

    ページングと並べ替えを簡単に追加するために、運用アプリでは、テーブルの代わりに商用の jQuery ベースのグリッド コントロールを使用する可能性があります。

    テーブルには、プロジェクト名、タスク名、チェック ボックス、実績作業時間、達成率、残存作業時間、割り当ての終了日の列が含まれます。 JavaScript 関数は、チェック ボックスと、各タスクの達成率のテキスト入力フィールドを作成します。

  • テキスト ボックスの 入力 要素は、選択したすべての割り当ての達成率を設定します。

  • button 要素は、状態の変更を送信します。

  • ボタン要素によってページが更新されます。

  • button 要素がアプリを終了し、Project Web Appの [タスク] ページに戻ります。

下部のテキスト ボックスとボタン要素は div 要素内にあり、CSS で UI オブジェクトの位置と外観を簡単に管理できます。 JavaScript 関数は、ページの下部に、状態更新の成功または失敗の結果を含む段落を追加します。

手順 2. HTML コンテンツを作成するには

  1. Visual Studio で、Default.aspx ファイルを開きます。

    ファイルには、2 つの asp:Content 要素が含まれています。属性を ContentPlaceHolderID="PlaceHolderAdditionalPageHead" 持つ要素はページ ヘッダー内に追加され、属性を ContentPlaceHolderID="PlaceHolderMain" 持つ要素はページ 本文 要素内に配置されます。

  2. ページ ヘッダーの <asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server"> コントロールで、Project Server コンピューター上の PS.js ファイルへの参照を追加します。 テストとデバッグには、PS.debug.js を使用できます。

      <script type="text/javascript" src="/_layouts/15/ps.debug.js"></script>
    

    アプリ インフラストラクチャは、IIS の /_layouts/15/ SharePoint サイトの仮想ディレクトリを使用します。 物理ファイルは です %ProgramFiles%\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\PS.debug.js

    注:

    運用環境用にアプリをデプロイする前に、スクリプト参照から削除 .debug してパフォーマンスを向上させます。

  3. ページ本文の <asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server"> コントロールで、生成された div 要素を削除し、UI オブジェクトの HTML コードを追加します。 table 要素にはヘッダー行のみが含まれます。 [タスク名] 列には、チェックボックス入力コントロールが含まれています。 キャプション要素のテキストは、App.js ファイルの getUserInfo 関数の onGetUserNameSuccess コールバックに置き換えられます。

    <form>
        <fieldset>
        <legend>Select assigned tasks</legend>
        <table id="assignmentsTable">
            <caption id="tableCaption">Replace caption</caption>
            <thead>
            <tr id="headerRow">
                <th>Project name</th>
                <th><input type="checkbox" id="headercheckbox" checked="checked" />Task name</th>
                <th>Actual work</th>
                <th>% complete</th>
                <th>Remaining work</th>
                <th>Due date</th>
            </tr>
            </thead>
        </table>
        </fieldset>
        <div id="inputPercentComplete" >
        Set percent complete for all selected assignments, or leave this
        <br /> field blank and set percent complete for individual assignments: 
        <input type="text" name="percentComplete" id="pctComplete" size="4"  maxlength="4" />
        </div>
        <div id="submitResult">
        <p><button id="btnSubmitUpdate" type="button" class="bottomButtons" ></button></p>
        <p id="message"></p>
        </div>
        <div id="refreshPage">
        <p><button id="btnRefresh" type="button" class="bottomButtons" >Refresh</button></p>
        </div>
        <div id="exitPage">
        <p><button id="btnExit" type="button" class="bottomButtons" >Exit</button></p>
        </div>
    </form>
    
  4. App.css ファイルに、UI 要素の位置と外観の CSS コードを追加します。 QuickStatus アプリの完全な CSS コードについては、「QuickStatus アプリのコード例」セクションを参照してください。

手順 3 では、割り当てを読み取り、テーブル行を作成し、割り当ての達成率を変更および更新するための JavaScript 関数を追加します。 実際の手順は、アプリの開発においてより反復的であり、HTML コードの一部の作成、関連するスタイルと JavaScript 関数の追加とテスト、HTML コードの変更または追加、プロセスの繰り返しを行います。

QuickStatus アプリの JavaScript 関数の作成

SharePoint アプリの Visual Studio テンプレートには、App.js ファイルが含まれています。このファイルには、SharePoint クライアント コンテキストを取得し、アプリ ページの基本的な取得および設定アクションを示す既定の初期化コードが含まれています。 SharePoint クライアント側 SP.js ライブラリの JavaScript 名前空間は SP です。 Project Server アプリは PS.js ライブラリを使用するため、アプリは PS 名前空間を使用してクライアント コンテキストを取得し、Project Server の JSOM にアクセスします。

QuickStatus アプリの JavaScript 関数には、次のものが含まれます。

  • ドキュメント の準備完了 イベント ハンドラーは、ドキュメント オブジェクト モデル (DOM) がインスタンス化されるときに実行されます。 準備完了イベント ハンドラーは、次の 4 つの手順を実行します。

    1. Project Server JSOM と pwaWeb グローバル変数のクライアント コンテキストを使用して、projContext グローバル変数を初期化します。

    2. getUserInfo 関数を呼び出して、projUser グローバル変数を初期化します。

    3. getAssignments 関数を呼び出します。この関数は、ユーザーに対して指定された割り当てデータを取得します。

    4. クリック イベント ハンドラーをテーブル ヘッダー チェック ボックスにバインドし、テーブルの各行のチェック ボックスにバインドします。 クリック イベント ハンドラーは、ユーザーがテーブル内の任意のチェック ボックスを選択またはクリアするときに、チェック ボックスのチェックされた属性を管理します。

  • getAssignments 関数が成功した場合は、onGetAssignmentsSuccess 関数を呼び出します。 この関数は、割り当てごとにテーブルに行を挿入し、各行の HTML コントロールを初期化してから、下部のボタン のプロパティを初期化します。

  • [更新] ボタンの onClick イベント ハンドラーは、updateAssignments 関数を呼び出します。 その関数は、選択した各割り当てに適用される達成率の値を取得します。または、達成率テキスト ボックスが空の場合、関数はテーブル内で選択した各割り当ての達成率を取得します。 updateAssignments 関数は、状態の更新を保存して送信し、結果に関するメッセージをページの下部に書き込みます。

手順 3. JavaScript 関数を作成するには

  1. Visual Studio で、App.js ファイルを開き、ファイル内のすべてのコンテンツを削除します。

  2. グローバル変数とドキュメント 準備完了 イベント ハンドラーを追加します。 ドキュメント オブジェクトには、jQuery 関数を使用してアクセスします。

    テーブル ヘッダー チェック ボックスの click イベント ハンドラーは、行チェックボックスのチェック状態を設定します。 すべての行チェックボックスが選択されているか、すべてオフになっている場合、行チェックボックスのクリック イベント ハンドラーは、ヘッダー チェック ボックスのチェック状態を設定します。 クリック イベント ハンドラーは、ページの下部にある結果メッセージも空の文字列に設定します。

     var projContext;
     var pwaWeb;
     var projUser;
     // This code runs when the DOM is ready and creates a ProjectContext object.
     // The ProjectContext object is required to use the JSOM for Project Server.
     $(document).ready(function () {
         projContext = PS.ProjectContext.get_current();
         pwaWeb = projContext.get_web();
         getUserInfo();
         getAssignments();
         // Bind a click event handler to the table header check box, which sets the row check boxes
         // to the checked state of the header check box, and sets the results message to an empty string.
         $('#headercheckbox').live('click', function (event) {
             $('input:checkbox:not(#headercheckbox)').attr('checked', this.checked);
             $get("message").innerText = "";
         });
         // Bind a click event handler to the row check boxes. If any row check box is cleared, clear
         // the header check box. If all of the row check boxes are selected, select the header check box.
         $('input:checkbox:not(#headercheckbox)').live('click', function (event) {
             var isChecked = true;
             $('input:checkbox:not(#headercheckbox)').each(function () {
                 if (this.checked == false) isChecked = false;
                 $get("message").innerText = "";
             });
             $("#headercheckbox").attr('checked', isChecked);
         });
     });
    
  3. クエリが成功した場合にGetUserNameSuccess を呼び出す getUserInfo 関数を追加します。 onGetUserNameSuccess 関数は、キャプション 段落の内容を、ユーザー名を含むテーブル キャプションに置き換えます。

         // Get information about the current user.
         function getUserInfo() {
             projUser = pwaWeb.get_currentUser();
             projContext.load(projUser);
             projContext.executeQueryAsync(onGetUserNameSuccess,
                 // Anonymous function to execute if getUserInfo fails.
                 function (sender, args) {
                     alert('Failed to get user name. Error: ' + args.get_message());
             });
         } 
         // This function is executed if the getUserInfo call is successful.
         function onGetUserNameSuccess() {
             var prefaceInfo = 'Assignments for ' + projUser.get_title();
             $('#tableCaption').text(prefaceInfo);
         }
    
  4. 割り当てクエリが成功した場合に onGetAssignmentsSuccess を呼び出す getAssignments 関数を追加します (手順 5 を参照)。 [含める] オプションは、指定されたフィールドのみを返すようにクエリを制限します。

     // Get the collection of assignments for the current user.
     function getAssignments() {
         assignments = PS.EnterpriseResource.getSelf(projContext).get_assignments();
         // Register the request that you want to run on the server. The optional "Include" parameter 
         // requests only the specified properties for each assignment in the collection.
         projContext.load(assignments,
             'Include(Project, Name, ActualWork, ActualWorkMilliseconds, PercentComplete, RemainingWork, Finish, Task)');
         // Run the request on the server.
         projContext.executeQueryAsync(onGetAssignmentsSuccess,
             // Anonymous function to execute if getAssignments fails.
             function (sender, args) {
                 alert('Failed to get assignments. Error: ' + args.get_message());
             });
     }
    
  5. onGetAssignmentsSuccess 関数を追加します。これにより、各割り当ての行がテーブルに追加されます。 prevProjName 変数は、行が別のプロジェクト用であるかどうかを判断するために使用されます。 その場合、プロジェクト名は太字で表示されます。そうでない場合、プロジェクト名は空の文字列に設定されます。

    注:

    JSOM には、ActualWorkTimeSpan など、CSOM に含まれる TimeSpan プロパティは含まれません。 代わりに、JSOM は PS などのミリ秒単位のプロパティを使用します 。StatusAssignment.actualWorkMilliseconds プロパティ。 そのプロパティを取得するメソッドは 、整数値を返すget_actualWorkMilliseconds。 >get_actualWork メソッドは、"3h" などの文字列を返します。 QuickStatus アプリではいずれかの値を使用できますが、表示方法は異なります。 割り当てクエリには両方のプロパティが含まれているため、デバッグ中に値をテストできます。 actualWork 変数を削除する場合は、割り当てクエリで ActualWork プロパティを削除することもできます。

    最後に、 onGetAssignmentsSuccess 関数は 、[更新 ] ボタンと [ 更新 ] ボタンをクリック イベント ハンドラーで初期化します。 [更新] ボタンのテキスト値は、HTML コードでも設定できます。

         // Get the enumerator, iterate through the assignment collection, 
         // and add each assignment to the table.
         function onGetAssignmentsSuccess(sender, args) {
             if (assignments.get_count() > 0) {
                 var assignmentsEnumerator = assignments.getEnumerator();
                 var projName = "";
                 var prevProjName = "3D2A8045-4920-4B31-B3E7-9D0C5195FC70"; // Any unique name.
                 var taskNum = 0;
                 var chkTask = "";
                 var txtPctComplete = "";
                 // Constants for creating input controls in the table.
                 var INPUTCHK = '<input type="checkbox" class="chkTask" checked="checked" id="chk';
                 var LBLCHK = '<label for="chk';
                 var INPUTTXT = '<input type="text" size="4"  maxlength="4" class="txtPctComplete" id="txt';
                 while (assignmentsEnumerator.moveNext()) {
                     var statusAssignment = assignmentsEnumerator.get_current();
                     projName = statusAssignment.get_project().get_name();
                     // Get an integer, such as 3600000.
                     var actualWorkMilliseconds = statusAssignment.get_actualWorkMilliseconds(); 
                     // Get a string, such as "1h". Not used here.
                     var actualWork = statusAssignment.get_actualWork();
                     if (projName === prevProjName) {
                         projName = "";
                     }
                     prevProjName = statusAssignment.get_project().get_name();
                     // Create a row for the assignment information.
                     var row = assignmentsTable.insertRow();
                     taskNum++;
                     // Create an HTML string with a check box and task name label, for example:
                     // <input type="checkbox" class="chkTask" checked="checked" id="chk1" /> <label for="chk1">Task 1</label>
                     chkTask = INPUTCHK + taskNum + '" /> ' + LBLCHK + taskNum + '">' 
                         + statusAssignment.get_name() + '</label>';
                     txtPctComplete = INPUTTXT + taskNum + '" />';
                     // Insert cells for the assignment properties.
                     row.insertCell().innerHTML = '<strong>' + projName + '</strong>';
                     row.insertCell().innerHTML = chkTask;
                     row.insertCell().innerText = actualWorkMilliseconds / 3600000 + 'h';
                     row.insertCell().innerHTML = txtPctComplete;
                     row.insertCell().innerText = statusAssignment.get_remainingWork();
                     row.insertCell().innerText = statusAssignment.get_finish();
                     // Initialize the percent complete cell.
                     $get("txt" + taskNum).innerText = statusAssignment.get_percentComplete() + '%'
                 }
             }
             else {
                 $('p#message').attr('style', 'color: #0f3fdb');     // Blue text.
                 $get("message").innerText = projUser.get_title() + ' has no assignments'
             }
             // Initialize the button properties.
             $get("btnSubmitUpdate").onclick = function() { updateAssignments(); };
             $get("btnSubmitUpdate").innerText = 'Update';
             $get('btnRefresh').onclick = function () { window.location.reload(true); };
             $get('btnExit').onclick = function () { exitToPwa(); };
         }
    
  6. [更新] ボタンのupdateAssignments クリック イベント ハンドラーを追加します。 ユーザーがタスクの達成率の値を変更したり、 percentComplete テキスト ボックスに値を追加したりすると、"60"、"60%"、"60%" などの複数の形式で値を入力できます。 getNumericValue メソッドは、入力テキストの数値を返します。

    注:

    運用環境用に設計されたアプリでは、数値情報の入力値にフィールド検証と追加のエラー チェックを含める必要があります。

    updateAssignments の例には、いくつかの基本的なエラー チェックが含まれており、ページの下部にあるメッセージ 段落に情報が表示されます。更新クエリが成功した場合は緑、入力エラーがある場合は赤、更新クエリが失敗した場合は赤です。

    submitAllStatusUpdates メソッドを使用する前に、アプリは PS を使用して更新プログラムをサーバーに保存する必要があります。StatusAssignmentCollection.update メソッド。

         // Update all checked assignments. If the bottom percent complete field is blank,
         // use the value in the % complete field of each selected row in the table.
         function updateAssignments() {
             // Get percent complete from the bottom text box.
             var pctCompleteMain = getNumericValue($('#pctComplete').val()).trim();
             var pctComplete = pctCompleteMain;
             var assignmentsEnumerator = assignments.getEnumerator();
             var taskNum = 0;
             var taskRow = "";
             var indexPercent = "";
             var doSubmit = true;
             while (assignmentsEnumerator.moveNext()) {
                 var pctCompleteRow = "";
                 taskRow = "chk" + ++taskNum;
                 if ($get(taskRow).checked) {
                     var statusAssignment = assignmentsEnumerator.get_current();
                     if (pctCompleteMain === "") {
                         // Get percent complete from the text box field in the table row.
                         pctCompleteRow = getNumericValue($('#txt' + taskNum).val());
                         pctComplete = pctCompleteRow;
                     }
                     // If both percent complete fields are empty, show an error.
                     if (pctCompleteMain === "" && pctCompleteRow === "") {
                         $('p#message').attr('style', 'color: #e11500');     // Red text.
                         $get("message").innerHTML =
                             '<b>Error:</b> Both <i>Percent complete</i> fields are empty, in row '
                             + taskNum
                             + ' and in the bottom textbox.<br/>One of those fields must have a valid percent.'
                             + '<p>Please refresh the page and try again.</p>';
                         doSubmit = false;
                         taskNum = 0;
                         break;
                     }
                     if (doSubmit) statusAssignment.set_percentComplete(pctComplete);
                 }
             } 
             // Save and submit the assignment updates.
             if (doSubmit) {
                 assignments.update();
                 assignments.submitAllStatusUpdates();
                 projContext.executeQueryAsync(function (source, args) {
                     $('p#message').attr('style', 'color: #0faa0d');     // Green text.
                     $get("message").innerText = 'Assignments have been updated.';
                 }, function (source, args) {
                     $('p#message').attr('style', 'color: #e11500');     // Red text.
                     $get("message").innerText = 'Error updating assignments: ' + args.get_message();
                 });
             }
         }
         // Get the numeric part for percent complete, from a string. For example, with "20 %", return "20".
         function getNumericValue(pctComplete) {
             pctComplete = pctComplete.trim();
             pctComplete = pctComplete.replace(/ /g, "");    // Remove interior spaces.
             indexPercent = pctComplete.indexOf('%', 0);
             if (indexPercent > -1) pctComplete = pctComplete.substring(0, indexPercent);
             return pctComplete;
         }
    
  7. ホスト Project Web App サイトの URL に SPHostUrl クエリ文字列パラメーターを使用する exitToPwa 関数を追加します。 [タスク] ページに戻すには、URL に追加 "/Tasks.aspx" します。 たとえば、 spHostUrl 変数は に https://ServerName/ProjectServerName/Tasks.aspx設定されます。

    getQueryStringParameter 関数は、QuickStatus ページの URL を分割して抽出し、URL オプションで指定されたパラメーターを返します。 ドキュメントの例を次に示 します。QuickStatus ドキュメントの URL 値 (すべて 1 行):

     https://app-ef98082fa37e3c.servername.officeapps.selfhost.corp.microsoft.com/pwa/
         QuickStatus/Pages/Default.aspx
         ?SPHostUrl=https%3A%2F%2Fsphvm%2D85178%2Fpwa
         &SPLanguage=en%2DUS
         &SPClientTag=1
         &SPProductNumber=15%2E0%2E4420%2E1022
         &SPAppWebUrl=https%3A%2F%2Fapp%2Def98082fa37e3c%2Eservername
             %2Eofficeapps%2Eselfhost%2Ecorp%2Emicrosoft%2Ecom%2Fpwa%2FQuickStatus
    

    前の URL の場合、 getQueryStringParameter 関数は SPHostUrl クエリ文字列値 https://ServerName/pwaを返します。

         // Exit the QuickStatus page and go back to the Tasks page in Project Web App.
         function exitToPwa() {
             // Get the SharePoint host URL, which is the top page of PWA, and add the Tasks page.
             var spHostUrl = decodeURIComponent(getQueryStringParameter('SPHostUrl'))
                             + "/Tasks.aspx";
             // Set the top window for the QuickStatus IFrame to the Tasks page.
             window.top.location.href = spHostUrl;
         }
         // Get a specified query string parameter from the {StandardTokens} URL option string.
         function getQueryStringParameter(urlParameterKey) {
             var docUrl = document.URL;
             var params = docUrl.split('?')[1].split('&');
             for (var i = 0; i < params.length; i++) {
                 var theParam = params[i].split('=');
                 if (theParam[0] == urlParameterKey)
                     return decodeURIComponent(theParam[1]);
             }
         }
    

この時点で QuickStatus アプリを発行してProject Web Appに追加した場合、アプリは [サイト コンテンツ] ページから実行できますが、ユーザーは簡単に使用できません。 ユーザーがアプリを見つけて実行できるように、[タスク] ページのリボンにボタンを追加できます。 手順 4 では、リボンのカスタム アクションを追加する方法を示します。

リボンのカスタム操作の追加

Project Web Appのリボン タブ、グループ、およびコントロールは、Project Server を実行しているコンピューター上のディレクトリにインストールされている pwaribbon.xml ファイルで[Program Files]\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\FEATURES\PWARibbon\listtemplates指定されます。 Project Web App リボンのカスタム アクションを設計するために、Project 2013 SDK のダウンロードには、pwaribbon.xml のコピーが含まれています。

Project Web Appは、Project Web App インスタンスでタイムシートとタスクの状態の両方の値を入力できる単一エントリ モードを使用するかどうかに応じて、[タスク] ページに異なるリボン定義を使用します。 Project Web Appの管理アクセス許可がある場合は、エントリ モードを決定するには、ページの右上隅にあるドロップダウン設定メニューで [PWA 設定] を選択します。 [PWA 設定] ページで、[タイムシートの設定と既定値] を選択し、ページの下部にある [シングル エントリ モード チェック] ボックスを確認します。

シングル エントリ モードがオフの場合、[タスク] ページのリボンは、pwaribbon.xml の [マイ ワーク] リージョンによって定義されます。

   <!-- REGION My Work Ribbon-->
   <CustomAction
      Id="Ribbon.ContextualTabs.MyWork"
      . . .

シングル エントリ モードがオンの場合、[タスク] ページ リボンは、pwaribbon.xml の [タイド モード] 領域によって定義されます。

   <!-- REGION Tied Mode Ribbon-->
   <CustomAction
      Id="Ribbon.ContextualTabs.TiedMode"
      . . .

各リージョンのグループとコントロールは似ていますが、関連付けられたモードのコントロールは、非関連付けモードの同じコントロールとは異なる関数を呼び出すことができます。 手順 4 では、シングル エントリ モードがオフのときに QuickStatus アプリのボタン コントロールを追加する方法を示します ([シングル エントリ モード] チェック ボックスはオフです)。

注:

リボンまたは SharePoint アプリケーションのメニューにカスタム アクションを追加する方法の一般的な情報については、「SharePoint 用アプリを使用して展開するカスタム アクションを作成する」を参照してください。

手順 4. リボンのカスタム アクションを [タスク] ページに追加するには

  1. Project Web Appの [タスク] ページでリボンを確認します。 リボンの [ タスク ] タブを選択し、変更方法を計画します。 送信タスク期間など、7 つのグループがあります。 [送信] グループには、[保存] ボタンと [送信状態] ドロップダウン メニューの 2 つのコントロールがあります。 グループ内の任意の場所にコントロールを追加したり、[ タスク ] タブの任意の場所に新しいコントロールを含むグループを追加したり、カスタム グループとコントロールを含む別のリボン タブを追加したりできます。 この例では、3 つ目のボタンを [送信] グループに追加します。ここで、ボタンは QuickStatus アプリの URL を呼び出します。

  2. Visual Studio の [ソリューション エクスプローラー] ウィンドウで、QuickStatus プロジェクトを右クリックし、新しい項目を追加します。 [ 新しい項目の追加 ] ダイアログ ボックスで、[ リボンのカスタム アクション ] を選択します (図 4 を参照)。 たとえば、カスタム アクションに RibbonQuickStatusAction という名前を付けて、[ 追加] を選択します。

    図 4. リボンのカスタム操作の追加

    リボン カスタム アクションの追加

  3. リボンのカスタム アクションの作成ウィザードの最初のページで、[ホスト Web] オプションを選択したままにして、カスタム アクション スコープのドロップダウン リストで [なし] を選択し、[次へ] を選択します (図 5 を参照)。 ドロップダウン リスト内の項目は、Project Server ではなく SharePoint に関連します。 生成された XML の大部分をカスタム アクションに置き換えて、Project Server に適用します。

    図 5. リボンのカスタム アクションにプロパティを指定

    リボン カスタム アクションのプロパティを指定

  4. リボンのカスタム アクションの作成ウィザードの次のページで、設定の既定値をすべて残し、[完了] を選択します (図 6 を参照)。 Visual Studio は、Elements.xml ファイルを含む RibbonQuickStatusAction フォルダーを作成します。

    図 6. ボタン コントロールに設定を指定

    ボタン コントロールの設定の指定 ボタン コントロールする

  5. リボン カスタム アクションの Elements.xml ファイルで既定で生成されたコードを変更します。 既定の XML コードを次に示します。

     <?xml version="1.0" encoding="utf-8"?>
     <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
         <CustomAction Id="21ea3aaf-79e5-4aac-9479-8eef14b4d9df.RibbonQuickStatusAction"
                     Location="CommandUI.Ribbon"
                     Sequence="10001"
                     Title="Invoke &apos;RibbonQuickStatusAction&apos; action">
         <CommandUIExtension>
             <!-- 
             Update the UI definitions below with the controls and the command actions
             that you want to enable for the custom action.
             -->
             <CommandUIDefinitions>
             <CommandUIDefinition Location="Ribbon.ListItem.Actions.Controls._children">
                 <Button Id="Ribbon.ListItem.Actions.RibbonQuickStatusActionButton"
                         Alt="Request RibbonQuickStatusAction"
                         Sequence="100"
                         Command="Invoke_RibbonQuickStatusActionButtonRequest"
                         LabelText="Request RibbonQuickStatusAction"
                         TemplateAlias="o1"
                         Image32by32="_layouts/15/images/placeholder32x32.png"
                         Image16by16="_layouts/15/images/placeholder16x16.png" />
             </CommandUIDefinition>
             </CommandUIDefinitions>
             <CommandUIHandlers>
             <CommandUIHandler Command="Invoke_RibbonQuickStatusActionButtonRequest"
                                 CommandAction="~appWebUrl/Pages/Default.aspx"/>
             </CommandUIHandlers>
         </CommandUIExtension >
         </CustomAction>
     </Elements>
    
    1. CustomAction 要素で、Sequence 属性と Title 属性を削除します。

    2. Submit グループにコントロールを追加するには、pwaribbon.xml ファイル内のコレクション内Ribbon.ContextualTabs.MyWork.Home.Groupsの最初のグループ (開始<Group Id="Ribbon.ContextualTabs.MyWork.Home.Page" Command="PageGroup" Sequence="10" Title="$Resources:pwafeatures,PAGE_PDP_CM_SUBMIT"する 要素) を見つけます。 Submit グループに子コントロールを追加するには、次のコードは、Elements.xml ファイル内の CommandUIDefinition 要素の正しい Location 属性を示しています。

        <CommandUIDefinitions>
          <CommandUIDefinition Location="Ribbon.ContextualTabs.MyWork.Home.Page.Controls._children">
             . . .
          </CommandUIDefinition>
        </CommandUIDefinitions>
      
    3. Button 要素の属性値を次のように変更します。

           <Button Id="Ribbon.ContextualTabs.MyWork.Home.Page.QuickStatus"
                   Alt="Quick Status app"
                   Sequence="30"
                   Command="Invoke_QuickStatus"
                   LabelText="Quick Status"
                   TemplateAlias="o1"
                   Image16by16="_layouts/15/1033/images/ps16x16.png" 
                   Image16by16Left="-80"
                   Image16by16Top="-144"
                   Image32by32="_layouts/15/1033/images/ps32x32.png" 
                   Image32by32Left="-32"
                   Image32by32Top="-288" 
                   ToolTipTitle="QuickStatus"
                   ToolTipDescription="Run the QuickStatus app" />
      
      • ボタンをグループ内の 3 番目のコントロールにするには、Sequence 属性を既存の Send Status コントロール (pwaribbon.xml の FlyoutAnchor 要素) の値よりSequence="20"大きい任意の数値にすることができます。 規則により、グループとコントロールのシーケンス番号は 10, 20, 30, …です。これにより、要素を中間位置に挿入できます。

      • Command 属性は、CommandUIHandler 要素で実行するコマンドを指定します (次の手順 5.d を参照)。 コマンド名を簡略化して、次の開発者が簡単にすることができます。 たとえば Command="Invoke_QuickStatus" 、 よりも読みやすいです Command="Invoke_RibbonQuickStatusActionButtonRequest"

      • イメージ属性は、ボタン コントロールの 16 x 16 ピクセル アイコンと 32 x 32 ピクセル アイコンを指定します。 既定の Elements.xml ファイルで、 Image32by32="_layouts/15/images/placeholder32x32.png" オレンジ色のドットを指定します。 Project Server を実行しているコンピューター上のディレクトリにインストールされている [Program Files]\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\LAYOUTS\1033\IMAGES イメージ マップ ファイル (ps16x16.png と ps32x32.png) からアイコンを抽出できます。 たとえば、32 x 32 ピクセル のアイコンは、左側のアイコンの 2 列目にあり、ps32x32.png イメージ マップの上部から下の 10 行目 (アイコンの上部は 9 行目の末尾の後にあります。9 行 x 32 ピクセル/行 = 288 ピクセル)。

      • ボタン コントロールのツール ヒントを表示するには、 ToolTipTitle 属性と ToolTipDescription 属性を追加します。

    4. CommandUIHandler 要素の属性を変更します。 たとえば、Command 属性が Button 要素の Command 属性値と一致していることを確認します。 CommandAction 属性の場合は、 ~appWebUrlQuickStatus Web ページの URL のプレースホルダーです。 リボン ボタンが QuickStatus アプリを呼び出すと、{StandardTokens} トークンは、SPHostUrl、SPLanguageSPClientTagSPProductNumberSPAppWebUrl を含む URL オプションに置き換えられます。

          <CommandUIHandlers>
              <CommandUIHandler Command="Invoke_QuickStatus"
                                CommandAction="~appWebUrl/Pages/Default.aspx?{StandardTokens}"/>
          </CommandUIHandlers>
      
  6. ソリューション エクスプローラーで Feature1.feature デザイナーを開き、[ソリューション] ウィンドウの [アイテム] から [機能] ウィンドウの [アイテム] に RibbonQuickStatusAction項目を移動します。 Package.package デザイナーを開くと、RibbonQuickStatusAction 項目が [パッケージ] ウィンドウの [アイテム] に表示されます。

アプリを開発してリボン ボタンを追加すると、通常はアプリをテストし、デバッグのために JavaScript コードにブレークポイントを設定します。 F5 キーを押してデバッグを開始すると、Visual Studio によってアプリがコンパイルされ、QuickStatus プロジェクトの [サイト URL] プロパティで指定されているサイトに展開され、アプリを信頼するかどうかを確認するページが表示されます。 続行してから QuickStatus アプリを終了すると、Project Web Appの [タスク] ページに戻ります。

注:

図 7 は、リボンの [タスク] タブの [クイック状態] ボタンが無効になっていることを示しています。 Visual Studio を使用した多数のデバッグデプロイの後、発行されたアプリを同じテスト サーバーでデバッグまたはデプロイし続けると、カスタム リボン コントロールをブロックできます。 ボタンを有効にするには、Visual Studio で RibbonQuickStatusAction 項目を削除し、別の名前と ID を持つ新しいリボン アクションを作成します。 それでも問題が解決しない場合は、Project Web Appテスト インスタンスからアプリを削除してから、別のアプリ ID でアプリを再作成してください。

図 7. 無効になっている [クイック状態] ボタンのヒントを表示する

無効なボタンのヒントの表示

手順 5 では、 QuickStatus アプリをデプロイしてインストールする方法を示します。 手順 6 では、アプリをインストールした後のテストに関するいくつかの追加の手順を示します。

QuickStatus アプリのデプロイ

Project Web Appなど、SharePoint Web アプリケーションにアプリをデプロイする方法はいくつかあります。 使用する展開は、アプリをプライベート SharePoint カタログに発行するか、パブリック Office ストアに発行するか、SharePoint がオンプレミスにインストールされているか、オンライン テナントであるかによって異なります。 手順 5 では、プライベート アプリ カタログ内のオンプレミス インストールに QuickStatus アプリをデプロイする方法を示します。 詳細については、「SharePoint 2013 用アプリのインストールと管理」および「SharePoint 用アプリの発行」を参照してください。

注:

SharePoint カタログにアプリを追加するには、SharePoint 管理者のアクセス許可が必要です。

手順 5. QuickStatus アプリをデプロイするには

  1. Visual Studio で、すべてのファイルを保存し、ソリューション エクスプローラーQuickStatus プロジェクトを右クリックし、[発行] を選択します

  2. QuickStatus アプリは SharePoint でホストされているため、発行するためのオプションはほとんどありません (図 8 を参照)。 [ Office および SharePoint 用アプリの発行 ] ダイアログ ボックスで、[完了] を選択 します

    図 8. QuickStatus アプリの発行

    発行ウィザードを使用

  3. QuickStatus.app ファイルを ~\QuickStatus\bin\Debug\app.publish\1.0.0.0 ディレクトリからローカル コンピューター上の便利なディレクトリ (またはオンプレミスインストール用の SharePoint コンピューター) にコピーします。

  4. SharePoint サーバーの全体管理で、サイド リンク バーで [ アプリ ] を選択し、[ アプリ カタログの管理] を選択します。

  5. アプリ カタログが存在しない場合は、「SharePoint 2013 でアプリ カタログを管理する」の「 Web アプリケーション用にアプリ カタログ サイトを構成 する」セクションに従って 、アプリ カタログのサイト コレクションを作成します。

    アプリ カタログが存在する場合は、[アプリ カタログの管理] ページでサイト URL に移動します。 たとえば、次の手順では、アプリ カタログ サイトは です https://ServerName/sites/TestApps

  6. アプリ カタログ ページで、サイド リンク バーで [ SharePoint 用アプリ ] を選択します。 [SharePoint 用アプリ] ページのリボンの [ ファイル ] タブで、[ ドキュメントのアップロード] を選択します。

  7. [ ドキュメントの追加 ] ダイアログ ボックスで、QuickStatus.app ファイルを参照し、バージョンのコメントを追加して、[ OK] を選択します

  8. アプリを追加するときに、アプリの説明、アイコン、その他の情報のローカル情報を追加することもできます。 [ Apps for SharePoint - QuickStatus.app ] ダイアログ ボックスで、SharePoint サイト コレクション内のアプリに表示する情報を追加します。 たとえば、次の情報を追加します。

    1. [簡単な説明 ] フィールド: 「クイック ステータス テスト アプリ」と入力します。

    2. [説明 ] フィールド: 「テスト アプリ」と入力して、複数のプロジェクトのタスクの達成率を更新します。

    3. アイコン URL フィールド: アプリ アイコンの 96 x 96 ピクセルの画像をアプリ カタログのサイト資産に追加します。 たとえば、 に移動しhttps://ServerName/sites/TestApps、[設定] ドロップダウン メニューで [サイト コンテンツ] を選択し、[サイト資産] を選択し、quickStatusApp.png イメージを追加します。 quickStatusApp 項目を右クリックし、[プロパティ] を選択し、[プロパティ] ダイアログ ボックスで [アドレス (URL)] の値をコピーします。 たとえば、 をコピー https://ServerName/sites/TestApps/SiteAssets/QuickStatusApp.pngし、[ アイコン URL ] Web アドレス フィールドに値を貼り付けます。 アイコンの説明 (図 9 のように) を入力し、「QuickStatus アプリ アイコン」と入力します。 URL が有効であることをテストします。

      図 9. QuickStatus アプリのアイコン URL の追加

      アプリの SharePoint でのプロパティの設定 アプリの

    4. [カテゴリ ] フィールド: 既存のカテゴリを選択するか、独自の値を指定します。 たとえば、「Statusing」と入力します。

      注:

      Statusing という名前のカテゴリは、テスト目的のためだけに使用されます。 Project Server アプリの一般的なカテゴリは 、Project Management です

    5. [発行元名 ] フィールド: 発行元の名前を入力します。 この例では、「Project SDK」と入力します。

    6. [有効] フィールド: インストールのためにサイト管理者Project Web Appアプリを表示するには、[有効なチェック] ボックスを選択します。

    7. その他のフィールドは省略可能です。 たとえば、アプリの詳細ページにサポート URL と複数のヘルプ イメージを追加できます。 図 9 の [Image URL 1 ] フィールドには、アプリのスクリーンショットの URL とスクリーンショットの説明が含まれています。

    8. [ Apps for SharePoint - QuickStatus.app ] ダイアログ ボックスで、[保存] を選択 します。 図 9 では、[SharePoint 用アプリ] ライブラリの [クイック ステータス更新] 項目が編集用にチェックアウトされているため、ダイアログ ボックス リボンの [編集] タブで、チェックインを選択してプロセスを完了します (図 10 を参照)。

      図 10. QuickStatus アプリが Apps for SharePoint ライブラリに追加されます。

      QuickStatus アプリが SharePoint に追加される

  9. Project Web Appの [設定] ドロップダウン メニューで、[アプリの追加] を選択します。 [アプリ] ページのサイド リンク バーで、[組織から] を選択し、クイック ステータス更新アプリの [アプリの詳細] を選択します。 図 11 は、前の手順で追加したアプリ アイコン、スクリーンショット、その他の情報を含む詳細ページを示しています。

    図 11. Project Web Appの [クイック ステータス更新プログラムの詳細] ページの使用

    QuickStatus アプリを Project Web Appに追加

  10. [クイック ステータス更新プログラムの詳細] ページで、[ IT の追加] を選択します。 Project Web App、QuickStatus アプリで実行できる操作を一覧表示するダイアログ ボックスが表示されます (図 12 を参照)。 操作の一覧は、AppManifest.xml ファイル内の AppPermissionRequest 要素から派生します。

    図 12. クイック ステータス アプリを信頼していることを確認する

    QuickStatus アプリの信頼の確認 QuickStatus アプリ

  11. [ クイック ステータス更新プログラムを信頼しますか ] ダイアログ ボックスで、[ 信頼する] を選択します。 アプリが [Project Web App サイト コンテンツ] ページに追加されます (図 13 を参照)。

    図 13. [サイト コンテンツ] ページでのクイック ステータス アプリの表示

    サイト コンテンツでの QuickStatus アプリの表示 サイト コンテンツ

[サイト コンテンツ] ページで、[ クイック ステータス更新] アイコンを選択してアプリを実行できます。

注:

アプリに関する情報を提供するその他のコマンドについては、[サイト コンテンツ] ページで、[クイック ステータス更新プログラム] の名前と省略記号 (...) を含むリージョンを選択します。アプリの [バージョン情報] ページを確認したり、アプリ エラーに関する情報を含む [アプリの詳細] ページを表示したり、アプリのアクセス許可ページを確認したり、アプリをProject Web Appから削除したりできます。

Project Web Appの [タスク] ページで (図 14 を参照)、リボンで [QuickStatus] ボタンを有効にする必要があります。 [クイック状態] ボタンが無効になっている場合は、図 7 のメモに記載されているアクションを試してください。

図 14. [タスク] タブから QuickStatus アプリを起動

[タスク] タブから QuickStatus アプリを起動する [タスク] タブ

手順 6 では、QuickStatus アプリで行うテストをいくつか示します。

QuickStatus アプリのテスト

ユーザーが QuickStatus アプリで試す可能性のあるすべての操作は、運用サーバーまたは Project Online の運用テナントにアプリを展開する前に、Project Server のテスト インストールでテストする必要があります。 テスト インストールを使用すると、実際のプロジェクトに影響を与えることなく、ユーザーの割り当てを変更および削除できます。 テストには、管理者、プロジェクト マネージャー、チーム メンバーなど、さまざまなアクセス許可セットを持つ複数のユーザーも含まれている必要があります。 徹底的なテストでは、開発中のテストでは明らかにされなかった、アプリで行う必要がある変更を明らかにできます。 手順 6 では 、QuickStatus アプリのいくつかのテストの一覧を示しますが、網羅的な一連のテストは含まれていません。

手順 6. QuickStatus アプリをテストするには

  1. ユーザーに割り当てがない QuickStatus アプリを実行します。 アプリは、ページの下部に青いメッセージを表示する必要があります。たとえば、 ユーザー名には割り当てがありません

    [ 更新] を選択すると、緑色の 割り当てに対するメッセージの変更が更新されました。

    注:

    アプリの動作は、割り当てがないときに [更新 ] ボタンが無効になるように変更する必要があります。

  2. ユーザーが複数の異なるプロジェクトで複数の割り当てを持ち、一部の割り当てが完了していないアプリを実行します。 アプリの外観に注目し、次のようにアクションを実行します (図 15 を参照)。

    1. onGetAssignmentsSuccess 関数は、現在のユーザーの割り当てごとにテーブルに行を作成します。 プロジェクト名は、各プロジェクトの最初の割り当てに対して、太字のフォントで 1 回だけ表示されます。

    2. [タスク名] 列ヘッダーの [チェック] ボックスをオフにします。 テーブル ヘッダーのクリック イベント ハンドラーは、タスク行の他のすべてのチェック ボックスをクリアします。

    3. すべてのタスクを選択します。 各行のクリック イベント ハンドラーは、すべての行を選択するかどうかを決定し、選択されている場合は、 タスク名 の列ヘッダーを選択します。

    4. すべてのチェックボックスをもう一度オフにし、残りの作業がある 1 つの割り当てを選択します。 たとえば、図 15 は、タスク T1 の残りの 20% を完了するタスクを示しています。

    5. [ 達成率の設定 ] テキスト ボックスに「80」と入力し、[ 更新] を選択します。 ページの下部に緑色のメッセージが表示されます。 割り当てが更新されました

      図 15. QuickStatus アプリでの割り当ての更新

      QuickStatus アプリでの割り当ての更新 QuickStatus アプリ

  3. [ 更新] を選択します (図 16 を参照)。 すべてのタスクが再び選択され、上位のタスクに 80% の完了が表示されます。

    図 16. [クイック ステータスの更新] ページを更新する

    QuickStatus ページを

  4. すべてのチェックボックスをオフにし、別のタスクを選択します。 たとえば、 PWA から [新しいタスク] を選択します。 [達成率の設定] テキスト ボックスは空のままにし、選択したタスクの [達成率] 列のすべてのテキストを削除して、[更新] を選択します。 両方のテキスト ボックスが空であるため、アプリに赤いエラー メッセージが表示されます (図 17 を参照)。

    図 17. エラー メッセージのテスト

    エラー メッセージのテスト エラー メッセージ

  5. 前のタスクを 80% 完了に更新し、[終了] を選択 しますexitToPwa 関数は、ブラウザー ウィンドウの場所を SharePoint ホスト アプリケーションの [タスク] ページに変更します (つまり、URL は に<https://ServerName/pwa/Tasks.aspx>変更されます)。 図 18 は、 T1 タスクと PWA タスクの新しいタスク がそれぞれ 80% 完了していることを示しています。

    図 18. タスクがProject Web Appで更新されていることを確認する

    Project Web Appで更新されたタスクを確認するProject Web Appする

  6. 更新された状態が 2013 Project Professionalに表示される前に、承認のために変更を送信してから、プロジェクト マネージャーによって承認する必要があります。

テストでは、使いやすさを向上させるために QuickStatus アプリで行う必要があるその他のいくつかの変更が明らかになります。 例:

  • テキスト ボックス値の追加のエラー チェックと検証が必要です。 現時点では、ユーザーは達成率に対して数値以外の値または負の値を入力できるため、エラー メッセージが表示されます。 たとえば、負の値の場合、エラー メッセージは [割り当ての更新中にエラーが発生しました]: PJClientCallableException: StatusingSetDataValueInvalid です

  • 空白のテキスト ボックスのエラー メッセージには、行番号に加えて、プロジェクトとタスクが一覧表示される場合があります。

  • 成功メッセージには、更新されたタスクの一覧が含まれる場合があります。または 、updateAssignments 関数が成功した場合は、ページの自動更新を実行し、更新されたタスクまたはパーセンテージを異なる色と太字のフォントで表示できます。

  • 非常に大きなテーブルを回避するには、割り当てのテーブルが 100% 未満のタスクに制限する必要があります。 または、すべてのタスクを表示するオプションを追加します。 この問題は、テーブルの代わりに jQuery ベースのグリッドを使用して解決することもできます。ここで、フィルター処理とグリッド ページングを簡単に実装できます。

  • QuickStatus アプリは状態を送信しないため、リボンの [タスク] タブの [クイック状態] アイコンは、論理的には [送信] グループの最後のアイコンではなく、[タスク] グループの最初のアイコンになります。

  • onGetAssignmentsSuccess 関数は btnSubmitUpdate ボタン テキストを初期化しますが、他のボタン テキスト値は HTML で初期化されるため、getAssignments 関数の実行中、ページは部分的に初期化された状態のままです。 テキスト値がすべて HTML で初期化されている場合、ページ上のボタンの一貫性が高くなります。

最も重要なのは、 QuickStatus アプリが使用するアプローチであり、割り当ての完了率が変更される場合は、運用アプリで変更する必要があります。 詳細については、「 次の手順 」セクションを参照してください。

QuickStatus アプリのコード例

Default.aspx ファイル

次のコードは、QuickStatus プロジェクトのファイルにありますPages\Default.aspx

    <%-- The following lines are ASP.NET directives needed when using SharePoint components --%>
    <%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, 
    Culture=neutral, PublicKeyToken=71e9bce111e9429c" MasterPageFile="~masterurl/default.master" Language="C#" %>
    <%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, 
    Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    <%@ Register TagPrefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, 
    Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    <%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, 
    Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    <%-- The markup and script in the following Content element will be placed in the <head> of the page.
        For production deployment, change the .debug.js JavaScript references to .js. --%>
    <asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <script type="text/javascript" src="../Scripts/jquery-1.7.1.min.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.runtime.debug.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.debug.js"></script>
    <script type="text/javascript" src="/_layouts/15/ps.debug.js"></script>
    <!-- CSS styles -->
    <link rel="Stylesheet" type="text/css" href="../Content/App.css" />
    <!-- Add your JavaScript to the following file -->
    <script type="text/javascript" src="../Scripts/App.js"></script>
    </asp:Content>
    <%-- The markup and script in the following Content element will be placed in the <body> of the page --%>
    <asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <form>
        <fieldset>
        <legend>Select assigned tasks</legend>
        <table id="assignmentsTable">
            <caption id="tableCaption">Replace caption</caption>
            <thead>
            <tr id="headerRow">
                <th>Project name</th>
                <th><input type="checkbox" id="headercheckbox" checked="checked" />Task name</th>
                <th>Actual work</th>
                <th>% complete</th>
                <th>Remaining work</th>
                <th>Due date</th>
            </tr>
            </thead>
        </table>
        </fieldset>
        <div id="inputPercentComplete" >
        Set percent complete for all selected assignments, or leave this
        <br /> field blank and set percent complete for individual assignments: 
        <input type="text" name="percentComplete" id="pctComplete" size="4"  maxlength="4" />
        </div>
        <div id="submitResult">
        <p><button id="btnSubmitUpdate" type="button" class="bottomButtons" ></button></p>
        <p id="message"></p>
        </div>
        <div id="refreshPage">
        <p><button id="btnRefresh" type="button" class="bottomButtons" >Refresh</button></p>
        </div>
    <div id="exitPage">
        <p><button id="btnExit" type="button" class="bottomButtons" >Exit</button></p>
    </div>
    </form>
    </asp:Content>

App.js ファイル

次のコードは、QuickStatus プロジェクトのファイルにありますScripts\App.js

    var projContext;
    var pwaWeb;
    var projUser;
    // This code runs when the DOM is ready and creates a ProjectContext object.
    // The ProjectContext object is required to use the JSOM for Project Server.
    $(document).ready(function () {
        projContext = PS.ProjectContext.get_current();
        pwaWeb = projContext.get_web();
        getUserInfo();
        getAssignments();
        // Bind a click event handler to the table header check box, which sets the row check boxes
        // to the selected state of the header check box, and sets the results message to an empty string.
        $('#headercheckbox').live('click', function (event) {
            $('input:checkbox:not(#headercheckbox)').attr('checked', this.checked);
            $get("message").innerText = "";
        });
        // Bind a click event handler to the row check boxes. If any row check box is cleared, clear
        // the header check box. If all of the row check boxes are selected, select the header check box.
        $('input:checkbox:not(#headercheckbox)').live('click', function (event) {
            var isChecked = true;
            $('input:checkbox:not(#headercheckbox)').each(function () {
                if (this.checked == false) isChecked = false;
                $get("message").innerText = "";
            });
            $("#headercheckbox").attr('checked', isChecked);
        });
    });
    // Get information about the current user.
    function getUserInfo() {
        projUser = pwaWeb.get_currentUser();
        projContext.load(projUser);
        projContext.executeQueryAsync(onGetUserNameSuccess,
            // Anonymous function to execute if getUserInfo fails.
            function (sender, args) {
                alert('Failed to get user name. Error: ' + args.get_message());
        });
    }
    // This function is executed if the getUserInfo call is successful.
    // Replace the contents of the 'caption' paragraph with the project user name.
    function onGetUserNameSuccess() {
        var prefaceInfo = 'Assignments for ' + projUser.get_title();
        $('#tableCaption').text(prefaceInfo);
    }
    // Get the collection of assignments for the current user.
    function getAssignments() {
        assignments = PS.EnterpriseResource.getSelf(projContext).get_assignments();
        // Register the request that you want to run on the server. The optional "Include" parameter 
        // requests only the specified properties for each assignment in the collection.
        projContext.load(assignments,
            'Include(Project, Name, ActualWork, ActualWorkMilliseconds, PercentComplete, RemainingWork, Finish, Task)');
        // Run the request on the server.
        projContext.executeQueryAsync(onGetAssignmentsSuccess,
            // Anonymous function to execute if getAssignments fails.
            function (sender, args) {
                alert('Failed to get assignments. Error: ' + args.get_message());
            });
    }
    // Get the enumerator, iterate through the assignment collection, 
    // and add each assignment to the table.
    function onGetAssignmentsSuccess(sender, args) {
        if (assignments.get_count() > 0) {
            var assignmentsEnumerator = assignments.getEnumerator();
            var projName = "";
            var prevProjName = "3D2A8045-4920-4B31-B3E7-9D0C5195FC70"; // Any unique name.
            var taskNum = 0;
            var chkTask = "";
            var txtPctComplete = "";
            // Constants for creating input controls in the table.
            var INPUTCHK = '<input type="checkbox" class="chkTask" checked="checked" id="chk';
            var LBLCHK = '<label for="chk';
            var INPUTTXT = '<input type="text" size="4"  maxlength="4" class="txtPctComplete" id="txt';
            while (assignmentsEnumerator.moveNext()) {
                var statusAssignment = assignmentsEnumerator.get_current();
                projName = statusAssignment.get_project().get_name();
                // Get an integer value for the number of milliseconds of actual work, such as 3600000.
                var actualWorkMilliseconds = statusAssignment.get_actualWorkMilliseconds();
                // Get a string value for the assignment actual work, such as "1h". Not used here.
                var actualWork = statusAssignment.get_actualWork();                         
                if (projName === prevProjName) {
                    projName = "";
                }
                prevProjName = statusAssignment.get_project().get_name();
                // Create a row for the assignment information.
                var row = assignmentsTable.insertRow();
                taskNum++;
                // Create an HTML string with a check box and task name label, for example:
                //     <input type="checkbox" class="chkTask" checked="checked" id="chk1" /> 
                //     <label for="chk1">Task 1</label>
                chkTask = INPUTCHK + taskNum + '" /> ' + LBLCHK + taskNum + '">'
                    + statusAssignment.get_name() + '</label>';
                txtPctComplete = INPUTTXT + taskNum + '" />';
                // Insert cells for the assignment properties.
                row.insertCell().innerHTML = '<strong>' + projName + '</strong>';
                row.insertCell().innerHTML = chkTask;
                row.insertCell().innerText = actualWorkMilliseconds / 3600000 + 'h';
                row.insertCell().innerHTML = txtPctComplete;
                row.insertCell().innerText = statusAssignment.get_remainingWork();
                row.insertCell().innerText = statusAssignment.get_finish();
                // Initialize the percent complete cell.
                $get("txt" + taskNum).innerText = statusAssignment.get_percentComplete() + '%'
            }
        }
        else {
            $('p#message').attr('style', 'color: #0f3fdb');     // Blue text.
            $get("message").innerText = projUser.get_title() + ' has no assignments'
        }
        // Initialize the button properties.
        $get("btnSubmitUpdate").onclick = function() { updateAssignments(); };
        $get("btnSubmitUpdate").innerText = 'Update';
        $get('btnRefresh').onclick = function () { window.location.reload(true); };
        $get('btnExit').onclick = function () { exitToPwa(); };
    }
    // Update all selected assignments. If the bottom percent complete field is blank,
    // use the value in the % complete field of each selected row in the table.
    function updateAssignments() {
        // Get percent complete from the bottom text box.
        var pctCompleteMain = getNumericValue($('#pctComplete').val()).trim();
        var pctComplete = pctCompleteMain;
        var assignmentsEnumerator = assignments.getEnumerator();
        var taskNum = 0;
        var taskRow = "";
        var indexPercent = "";
        var doSubmit = true;
        while (assignmentsEnumerator.moveNext()) {
            var pctCompleteRow = "";
            taskRow = "chk" + ++taskNum;
            if ($get(taskRow).checked) {
                var statusAssignment = assignmentsEnumerator.get_current();
                if (pctCompleteMain === "") {
                    // Get percent complete from the text box field in the table row.
                    pctCompleteRow = getNumericValue($('#txt' + taskNum).val());
                    pctComplete = pctCompleteRow;
                }
                // If both percent complete fields are empty, show an error.
                if (pctCompleteMain === "" && pctCompleteRow === "") {
                    $('p#message').attr('style', 'color: #e11500');     // Red text.
                    $get("message").innerHTML =
                        '<b>Error:</b> Both <i>Percent complete</i> fields are empty, in row '
                        + taskNum
                        + ' and in the bottom textbox.<br/>One of those fields must have a valid percent.'
                        + '<p>Please refresh the page and try again.</p>';
                    doSubmit = false;
                    taskNum = 0;
                    break;
                }
                if (doSubmit) statusAssignment.set_percentComplete(pctComplete);
            }
        } 
        // Save and submit the assignment updates.
        if (doSubmit) {
            assignments.update();
            assignments.submitAllStatusUpdates();
            projContext.executeQueryAsync(function (source, args) {
                $('p#message').attr('style', 'color: #0faa0d');     // Green text.
                $get("message").innerText = 'Assignments have been updated.';
            }, function (source, args) {
                $('p#message').attr('style', 'color: #e11500');     // Red text.
                $get("message").innerText = 'Error updating assignments: ' + args.get_message();
            });
        }
    }
    // Get the numeric part for percent complete, from a string. 
    // For example, with "20 %", return "20".
    function getNumericValue(pctComplete) {
        pctComplete = pctComplete.trim();
        pctComplete = pctComplete.replace(/ /g, "");    // Remove interior spaces.
        indexPercent = pctComplete.indexOf('%', 0);
        if (indexPercent > -1) pctComplete = pctComplete.substring(0, indexPercent);
        return pctComplete;
    }
    // Exit the QuickStatus page and go back to the Tasks page in Project Web App.
    function exitToPwa() {
        // Get the SharePoint host URL, which is the top page of PWA, and add the Tasks page.
        var spHostUrl = decodeURIComponent(getQueryStringParameter('SPHostUrl'))
                        + "/Tasks.aspx";
        // Set the top window for the QuickStatus IFrame to the Tasks page.
        window.top.location.href = spHostUrl;
    }
    // Get a specified query string parameter from the {StandardTokens} URL option string.
    function getQueryStringParameter(urlParameterKey) {
        var docUrl = document.URL;
        var params = docUrl.split('?')[1].split('&');
        for (var i = 0; i < params.length; i++) {
            var theParam = params[i].split('=');
            if (theParam[0] == urlParameterKey)
                return decodeURIComponent(theParam[1]);
        }
    }

App.css ファイル

次の CSS コードは、QuickStatus プロジェクトのファイルにありますContent\App.css

    /* Custom styles for the QuickStatus app. */
    /*============= Table elements ========================================*/
    table {
        width: 90%;
    }
    caption {
        font-size: 16px;
        padding-bottom: 5px;
        font-weight: bold;
        color: gray;
    }
    table th {
        background-color: gray;
        color: white;
    }
    table td, th {
        width: auto;
        text-align: left;
        padding: 2px;
        border: solid 1px whitesmoke;
        color: gray;
    }
    /*=== Class for check boxes added to rows 
    */
    .chkTask {
        width: 12px;
        height: 12px;
        color: gray;
    }
    /*========== DIV id for the Percent Complete text box ================*/
    #inputPercentComplete {
        position: fixed;
        top: auto;
        height: auto;
        padding-top: 20px;
        margin-left: 30px;
    }
    /*========== DIV id for the Submit Result button ====================*/
    #submitResult {
        position: fixed;
        top: auto;
        height: auto;
        padding-top: 60px;
    }
    /*========== DIV id for the Refresh Page button ====================*/
    #refreshPage {
        position: fixed;
        top: auto;
        height: auto;
        padding-top: 60px;
        margin-left: 120px;
    }
    /*========== DIV id for the Exit Page button ====================*/
    #exitPage {
        position: fixed;
        top: auto;
        height: auto;
        padding-top: 60px;
        margin-left: 240px;
    }
    /*========== Class for the buttons at the bottom of the page =======*/
    .bottomButtons {
        color: gray;
        font-weight: bold; 
        font-size: 12px; 
        border-color: darkgreen;
        border-width: thin;
    }

リボンの Elements.xml ファイル

次の XML 定義はRibbonQuickStatusAction\Elements.xml、リボンの [タスク] タブの [タスク] ボタンに対して、QuickStatus プロジェクトのファイルにあります。

    <?xml version="1.0" encoding="utf-8"?>
    <Elements xmlns="http://schemas.microsoft.com/sharepoint/">
    <CustomAction Id="21ea3aaf-79e5-4aac-9479-8eef14b4d9df.RibbonQuickStatusAction"
                    Location="CommandUI.Ribbon">
        <CommandUIExtension>
        <!-- 
        Add a button that invokes the QuickStatus app. The Quick Status button is displayed as  
        the third control in the Page group (the group title is "Submit").
        -->
        <CommandUIDefinitions>
            <CommandUIDefinition Location="Ribbon.ContextualTabs.MyWork.Home.Page.Controls._children">
            <Button Id="Ribbon.ContextualTabs.MyWork.Home.Page.QuickStatus"
                    Alt="Quick Status app"
                    Sequence="30"
                    Command="Invokae_QuickStatus"
                    LabelText="Quick Status"
                    TemplateAlias="o1"
                    Image16by16="_layouts/15/1033/images/ps16x16.png" 
                    Image16by16Left="-80"
                    Image16by16Top="-144"
                    Image32by32="_layouts/15/1033/images/ps32x32.png" 
                    Image32by32Left="-32"
                    Image32by32Top="-288" 
                    ToolTipTitle="Quick Status"
                    ToolTipDescription="Run the QuickStatus app" />
            </CommandUIDefinition>
        </CommandUIDefinitions>
        <CommandUIHandlers>
            <CommandUIHandler Command="Invoke_QuickStatus"
                            CommandAction="~appWebUrl/Pages/Default.aspx?{StandardTokens}"/>
        </CommandUIHandlers>
        </CommandUIExtension >
    </CustomAction>
    </Elements>

AppManifest.xml ファイル

QuickStatus プロジェクトのアプリ マニフェストの XML を次に示します。これには、複数のプロジェクトでアプリ ユーザーの割り当て状態を更新するために必要な 2 つのアクセス許可要求スコープが含まれています。

    <?xml version="1.0" encoding="utf-8" ?>
    <!--Created:cb85b80c-f585-40ff-8bfc-12ff4d0e34a9-->
    <App xmlns="http://schemas.microsoft.com/sharepoint/2012/app/manifest"
        Name="QuickStatus"
        ProductID="{bbc497e7-1221-4d7b-a0ae-141a99546008}"
        Version="1.0.0.0"
        SharePointMinVersion="15.0.0.0"
    >
    <Properties>
        <Title>Quick Status Update</Title>
        <StartPage>~appWebUrl/Pages/Default.aspx?{StandardTokens}</StartPage>
    </Properties>
    <AppPrincipal>
        <Internal />
    </AppPrincipal>
    <AppPermissionRequests>
        <AppPermissionRequest Scope="https://sharepoint/projectserver/statusing" Right="SubmitStatus" />
        <AppPermissionRequest Scope="https://sharepoint/projectserver/projects" Right="Read" />
    </AppPermissionRequests>
    </App>

AppIcon.png ファイル

QuickStatus アプリの完全な Visual Studio ソリューションには、カスタム AppIcon.png ファイルが含まれています。 このソリューションは、Project 2013 SDK のダウンロードに含まれます。

次の手順

QuickStatus アプリは、Project Server 2013 とProject Onlineにインストールできるアプリを記述する方法の比較的簡単な例です。 [ QuickStatus アプリのテスト ] セクションには、使いやすさを向上するために行うことができるいくつかの機能強化が示されています。 QuickStatus アプリでは、JavaScript 関数を使用して、Project Web Appの割り当て状態を更新します。 ただし、割り当ての達成率を変更することは、推奨されるプロジェクト管理プラクティスではありません。 もう 1 つの方法は、割り当てられたタスクの実際の開始日と残りの期間を更新することです。 問題の詳細については、MPUG ニュースレターの 「更新の改善 」を参照してください。

関連項目