SharePoint2013 & Knockout.js で遊んでみた(その3)

今さら言うのもなんですが、やったことをブログに書き起こしてみると自分がいかによくわからないままなんとな~く作っていたかがわかりますね・・・
(しかしそれでも動いてしまうのだから、システムって恐ろしい)

それはさておき、このシリーズの最後の投稿です。
前回View部分を定義しましたので、次にViewModelを定義します。
このポストをおおいにパクって参考にしてます)

function Link(data) {
    this.SiteName = ko.observable(data.SiteName);
    this.SiteURL = ko.observable(data.SiteURL);
    this.SortOrder = ko.observable(data.SortOrder);
    this.ID = ko.observable(data.ID);
}

function LinkModel() {

    var self = this;
    self.Links = ko.observableArray([]);

    // get my personal link.
    $().SPServices({
        operation: "GetListItems",
        async: false,
        webURL: ”MySiteのURL”,
        listName: "MyLinks",
        CAMLViewFields: "<ViewFields Properties='True' />",
        CAMLQuery: "<Query><OrderBy><FieldRef Name='SortOrder'/></OrderBy></Query>",
        completefunc: function (xData, Status) {
            var spsData = $(xData.responseXML).SPFilterNode("z:row").SPXmlToJson({ includeAllAttrs: true, removeOws: true });
            if (spsData) {
                $.each(spsData, function (k, l) {
                    var arrOrder = (l.SortOrder + "").split(".");
                    self.Links.push(new Link({
                        SiteName: l.Title,
                        SiteURL: l.SiteURL,
                        SortOrder: arrOrder[0],
                        ID: l.ID
                    }))
                });
            } // end if
        } // end complete func
    }); // end SPServices

    self.addSite = function() {
        self.Links.push({
            SiteName: "",
            SiteURL: "",
            SortOrder: "",
            ID: "New"
        });
    };

    self.removeSite = function(data){
        if (data.ID !== "New") {
            var batch = "<Method ID='1' Cmd='Delete'><Field Name='ID'>" + data.ID() + "</Field></Method>";
            $().SPServices({
                operation: "UpdateListItems",
                async: false,
                webURL: ”MySiteのURL”, 
                listName: "MyLinks",
                updates: "<Batch OnError='Continue'>" + batch + "</Batch>",
                completefunc: function (xData, Status) {
                    alert("completed");
                }
            });
        };
        self.Links.remove(data);
    }; // end removeSites

    self.saveSites = function(){
        var ret = self.Links();
        var i = 1;
        var batch = "";
        for(var n = 0, len = ret.length; n < len; n++){
            if(ret[n].ID != "New"){
                batch += "<Method ID='" + i + "' Cmd='Update'><Field Name='ID'>" + ret[n].ID() + "</Field><Field Name='SortOrder'>" + ret[n].SortOrder() + "</Field><Field Name='Title'>" + ret[n].SiteName() + "</Field><Field Name='SiteURL'>" + ret[n].SiteURL() + "</Field></Method>";
            } else {
                batch += "<Method ID='" + i + "' Cmd='New'><Field Name='ID'>" + ret[n].ID + "</Field><Field Name='SortOrder'>" + ret[n].SortOrder + "</Field><Field Name='Title'>" + ret[n].SiteName + "</Field><Field Name='SiteURL'>" + ret[n].SiteURL + "</Field></Method>";
            }/
            i++;
        }

        $().SPServices({
            operation: "UpdateListItems",
            async: false,
            webURL: ”MySiteのURL”, 
            listName: "MyLinks",
            updates: "<Batch OnError='Continue'>" + batch + "</Batch>",
            completefunc: function (xData, Status) {
                alert("completed");
            }
        });
    }; // end saveSites

} // end LinkModel

ここで、”observable”、”observableArray”っていうのが出てきますが、これは特殊な JavaScript オブジェクトで、ViewModelのプロパティの変更をViewに知らせることができます(逆もまたしかり)。”observable”は単一のオブジェクト、”observableArray”は配列に対し使います。
こう書くことでViewModel側でViewに表示させるべきデータが変われば自動的にViewが変わるので、スクリプト内でHTMLを書き換える必要がありません。
追加の場合はViewとバインドしてる配列に新しい要素を追加し、削除の場合は要素を削除、更新の場合は配列の中身をwebサービスでしぇあぽにばばーっと送っているだけです。DOM要素は一切さわってません。

なお、observableについて詳しくは本家のドキュメントをみてください。

さいごに、ViewとViewModelを関連付けます。これは簡単。

$(document).ready(function () {
    ko.applyBindings(new LinkModel());
});

あら不思議、これでKnockout.jsをつかって自前画面からSharePointのリストを操作できるようになりました!

しかし心残りなのは、更新処理のときに差分ではなく全部更新になっていること。アイテム数が少なければ全とっかえでもいいですが、数が増えるとどうなんだ・・・という感じです。
差分をとるうまいやり方を思いつけず今回はこの形ですが、できれば差分更新にしたいところ。よいアイデアあれば教えてください。
あ、あと、エラーとか例外とかいっさい処理してませんのであしからず(笑)

結局よくわからなかったところは、Modelは出てこなかったけど?!ってことです(笑)
SharePointで作る場合、何をViewModelとし、何をModelとするのか、自分の中で整理がついてないので、今後の課題ですね・・・

SharePoint2013 & Knockout.js で遊んでみた(その2)

前回の続きです。

いまさらですが、Knockout.jsってなんなのってあたりをおさらい。

Knockout.jsとは、MVVM(Model-View-View Model)設計パターンに基づいたアプリケーションの構築をサポートするJava Scriptフレームワークです。
特徴をKnockout.js日本語サイトからまんま引用しますと、

宣言型バインディング
UIに必要なのは ViewModel (シンプルなモデルオブジェクト) とデータバインドだけ。
ややこしいDOM操作なしで、動的なインターフェイスを作ることができます。
UIの自動更新
ViewModel のプロパティが変更されると、自動的にUIの関連付けられた部分を更新します。
依存関係のトラッキング
データの結合や変換を実現するためのデータ間の関係チェーンを暗黙的に設定します。
UIテンプレート
幾重にもネストされたテンプレートも、バインドされた ViewModel を用いて素早くUIを生成します。

MVVMって・・・とか深いところはとても説明できる自信がないので、興味があればこの辺とかみておいてください。
http://www.buildinsider.net/web/bookjslib111/89

SharePoint2013になってクライアントサイドでの自由度はかなり上がりましたが、とはいえ既存のwebパーツを使わず、自前の画面でSharePointとどうたらこうたらしようとなると、jQueryだけではかなり苦しくなってきます。画面構築とロジックが絡まりあって、コードの見通しも悪くなりますしね・・・
そういった面で、Knockout.jsを使うとスクリプト内でのDOM操作が必要なくなる、というのはかなり魅力です。スクリプト内では、画面のことは考えずデータ操作とイベント処理だけに専念すればよいわけです。
また、ユーザーさんとやり取りしながら作ってるとよくありがちな、度重なる画面仕様の変更にも強い(はず)です(スクリプト内でDOMを生成してたりすると、あとで必ずといってよいほど泣きを見るので・・・)

さて、前置きはこの辺で。
Knockout.jsをつかってなんかするぞー!という場合、最低限以下の作業が必要になります。

  • Knockout.js本体の読み込み(当たり前ですね?!)
  • Viewの定義(基本、画面のHTMLです)
  • ViewModelの定義(Viewに表示するデータの操作やViewから呼び出されるイベントハンドラを書きます)
  • ViewとViewModelの関連付け

Knockout.jsの読み込みは割愛させていただいて(すいません・・・)、まずViewを定義します。

<div class="MyLinkDiv">
    <table>
        <thead>
            <tr>
                <th>ID&</th>
                <th>Name</th>
                <th>URL</th>
                <th>Order</th>
                <th></th>
            </tr>
        </thead>
        <tbody data-bind='foreach: Links'>
            <tr>
                <td data-bind='text:ID'></td>
                <td><input data-bind='value: SiteName' /></td>
                <td><input data-bind='value: SiteURL' /></td>
                <td><input data-bind='value: SortOrder' /></td>
                <!-- 削除ボタン -->
                <td><input type='button' value='Delete' data-bind='click: $root.removeSite'/><td>
            </tr>
        </tbody>
    </table>
    <!-- 新規アイテム追加ボタン -->
    <input type='button' value='Add New' data-bind='click: addSite' />
    <!-- 保存ボタン -->
    <input type='submit' value='Save Links' data-bind='click: saveSites' />
</div>

ここでは”data-bind”という属性がキモになります。
inputタグにdata-bind=’value: SiteName’という記述がありますが、これで「このinputのvalueと、View Modelで定義したSiteNameプロパティをひもづけるよ!」と定義してることになります。
ここの”value”を「バインディング」といい、Knockoutではいろいろと用意されています。また、SiteNameの方はここではプロパティですが、そのほか式などもひもづけできます。

また、”foreach”バインディングを使うことで、テーブルやリストといった繰り返し処理の必要なマークアップとView Model内の配列をひもづけることができます。上記のように書くことで、配列内の要素ぶんだけViewのテーブルに行が出力されることになります。

あ、あとですね、”$root”というのは「ルートコンテキストにあたるメインの ViewModel であり、最上位のコンテキスト」ってことなんですが、まあここではView Modelの”removeSite”っていう関数を呼び出してるんだと便宜的に思っといてください。
(バインディングコンテキストについては、詳しくはこちらをみてください)

なお、データバインディングについてもっと知りたい場合は、先ほど紹介した日本語サイトや、本家のドキュメントを参照してください。

おっと、意外と長くなってしまった・・・なかなか佳境に入りませんが、今回はこの辺で。

て、今回はまったくSharePoint出てきませんでしたね?!(汗)。次回はきっと登場します・・・

SharePoint2013 & Knockout.js で遊んでみた(その1)

ひさびさのSharePoint2013ネタです。長くなりそうなので、何回かに分けてお送りします(の予定)。
今回は下準備ということで、Knockout使い始めるまでのあれこれです。

今はやりのJavaScriptのMVC系フレームワーク使ってなんかやってみたいな~、と思いたち、お勉強も兼ねちょっと遊んでみました。

【実現したいこと】
・ユーザーさんが個別にカスタマイズできるリンク集的なウィジェットを作る
・リンクのデータは各ユーザの個人サイトに置く
・登録編集はそのリンクリストを直でいじるんではなく、ポータル内にインターフェースを置く

まあー表示くらいはwebサービスなりでデータをとってきて、jQueryでDOMをがりがり・・・でできなくはないですが、リンクの登録編集とかの実装になると気が遠くなりそうですよね?!私はなりそうです。
で、これはひょっとしてフレームワークの出番ではないか?!と短絡的に考えました。

最終的に、登録編集フォームとしてこんな感じのものを作ってみました。(小さくてすいません)
140521_2

さて、手始めに同じことを考えてる人はいないだろうか?いやいるはず!世界は広い!といつものようにググってみたら、こんなものが。
http://sympmarc.com/2013/09/10/spservices-stories-16-beginning-sharepoint-development-with-kosp-knockout-for-sharepoint-rest-api-and-sp-services/

おおっ。私の愛するSPServicesを使ってできそうではないですか。
とはいうものの、Knockout.js使ったことないので(汗)、とりあえず本家サイトでお勉強します。
http://knockoutjs.com/

いろんなとこで書かれてますが、ほんとここのチュートリアルは良くできてます。触ってて楽しい。いつまでも遊んでたい・・・のですが、そうしてると仕事にならないので適当なところで切り上げます(涙。

しかし困ったことに、上の記事はデータの表示方法のサンプルです。
やりたいのは登録・編集・削除だし、どうしよう~、と調べてたら本家サイトのExampleが使えそうだったので、これを参考に実装してみることに。
http://knockoutjs.com/examples/gridEditor.html
SPServicesを使ってのリストのアップデートはこの辺を参考にすればよさそう。
http://spservices.codeplex.com/wikipage?title=UpdateList&referringTitle=Lists

方針の決まったところで、おもむろに必要なファイルをDLします。
Knockout.js※導入方法もこのページに載ってますね。
http://knockoutjs.com/downloads/index.html
SPServices
http://spservices.codeplex.com/
KoSpJs
http://kosp.codeplex.com/

KoSpJsは、

KoSp provides custom knockout binding handlers that enables easy binding of json data from SharePoint lists retrieved via REST API with oData queries and  SP Services with CAML queries with client side controls.

との触れ込みなので実装が楽になるかも?と入れてみました。上で参照してる記事でも使ってますしね・・・

で、いつものようにDLしたファイルをカスタマイズファイル専用置場へアップロードしておきます。あ、jQueryもアップロードしてない場合はお忘れなく。

さいごに、自分の個人用サイトのなかにリンクを登録するリストをあらかじめ作っておきます。
これはもうカスタムリストでちゃっちゃと作りました。

リストタイトル:MyLinks
追加した列:
SiteURL (Single line of text)
SortOrder (Number)

これで、下準備は完了です(のはず)。

今回はこの辺で。続きはまた・・・

サイトのタイトルを自前のロゴ画像で置き換える

SharePoint2010のチームサイト、サイトタイトルの表示がそっけなくてさびしいですよね。
140519

アイコンはサイトの設定で変えられますが、それ以外は普通はいじれません。
そこで、CSSを使ってここに自前のタイトルロゴ画像を貼ってみることにしました。

140519_2

ついでに背景に画像も表示してみましたが、これはやりすぎかもですね(笑)

さて、CSSはこんな感じです。

/*タイトルロゴを貼る*/
.s4-titletext
{
    background-image: url('ロゴ画像のURL');
    background-repeat: no-repeat;
    background-position: left;
}
.s4-title h1, .s4-title h2, .s4-title span
{
    display: none;
}
/* タイトルエリアに背景画像を設定 */
.s4-title
{
    background: url('背景画像のURL') #fff repeat-x !important;
}

あと、サイトのアイコンをサイトの設定から変えられない環境でも、以下のCSSで(無理やり)変えられます。

.s4-titlelogo a
{
    display: block;
    width: 105px; /* 置き換える画像の幅に合わせ指定 */
    height: 70px; /* 置き換える画像の高さに合わせ指定 */
    overflow: hidden;
    background: url(’置き換える画像のURL’) no-repeat;
}
.s4-titlelogo > a > img
{
    height: 0;
    padding-top: 80px; /* 置き換える画像の高さ+αで適宜微調整してください */
}

ちなみに、上のロゴ画像はコチラのジェネレータで作りましたっ。

http://supalogo.com/

サイドリンクバーのスタイルをCSSで変更する

SharePoint2010のカスタマイズです。

チームサイトのデフォルトのサイドリンクバーって、文字ばっかりでちょっとさびしいですよね?
pict0512-1

そこで、ヘッダーとかリストマークとかCSSでいろいろいじくってみました。
pict0512-2

ソースコードを公開しますので、ご自由にお使いください。

.s4-ql ul.root ul
{
    margin-bottom: 5px;
}

#s4-leftpanel-content
{
    background-color: #FFFFFF !important;
    border: 1px #d8d6d7 solid !important;
    border: 0px !important;
}

.menu-vertical
{
    padding-right: 10px !important;
    padding-left: 10px !important;
}

.menu-vertical > ul.root
{
    padding-top: 5px;
}

/* mainlinks */
.menu-vertical > ul.root > li.static > .menu-item
{
    padding: 2px 2px 2px 10px !important;
    border-bottom: 1px #663366 solid !important;
    border-left: 4px #663366 solid !important;
    color: #333 !important;
    font-weight: bold;
    background-color: #FAF0E6;
    margin-top: 5px;
    margin-bottom: 1px;
}

/* Sublinks */
.menu-vertical > ul.root > li.static > ul.static > li.static
{
    margin-bottom: 5px;
}

.menu-vertical > ul.root > li.static > ul.static
{
    margin-top: 5px;
    margin-bottom: 10px !important;
}

.menu-vertical > ul.root > li.static > ul.static > li.static > a > span > span
{
    margin-top: 0px;
}

/* Selected  Style*/
.menu-vertical > ul.root > li.selected > a
{
    background-color: transparent !important;
    border: 0px;
    margin: 0px;
    padding: 0px;
}

.menu-vertical > ul.root > li.static > ul.static > li > a.selected
{
    background-image: none !important;
    background-color: transparent;
    color: fuchsia !important;
    border: 0px;
    margin-top: 1px;
}

.s4-specialNavLinkList
{
    border: 0px;
}

/* Liststyle */
.s4-ql ul.root ul > li
{
    padding-top: 0px;
    background-image: url('リストマークの画像ファイルURL');
    background-repeat: no-repeat;
    background-position: 0.0em;
}

/* List links */
.s4-ql ul.root ul > li > a
{
    display: inline-block !important;
    padding: 2px 0px 0px 16px !important;
    vertical-align: top !important;
}

色使いやリストマークの画像を適宜変えれば、クールなイメージにもできますね。

編集フォームのボタンの表示テキストを変更する

SharePoint2010で申請ワークフローをつくったときのお話です。

デザイナーワークフローで、アイテムの保存時に承認依頼メールが飛ぶ仕様にしましたが、保存すると申請が承認者にいくんだよーということがユーザーにわかるようにしたい。
つまり、↓このボタンの表示テキストを「保存」ではなく「申請」に変えたいと・・・
pict0509

そこで、例によってjQueryでさくーっと書き換えてみました。

$("input[id$='diidIOSaveItem']").attr("value", "申請");

申請ステータスによって条件分岐し表記を変えれば、さらにきめ細かい制御ができますね。

さらに、

$("#s4-ribbonrow").css("display", "none");

のコードを加えて、フォーム上のリボンを非表示にしてあげてもいいですね。

なお、承認後は申請者に再編集させたくないのでボタンを無効化したいんだけど・・・なんてときは、以下のエントリーが参考になるかもです。
編集フォームでフィールドをRead Onlyにする

サイドリンクバーで「最近の変更」を非表示にする

連休最終日に大好きなアーティストのLIVEに行ってきて寝不足なうえ、休みボケでまっったくエンジンがかからないので、本日は小ネタでお許しください。

SharePoint2010で、ホームページでないwikiページを開くとサイドリンクバーに表示される「最近の変更」というやつ。

pict0507

サイドリンクバー自体は非表示にしたくないんだけど、これはいらないんだよね、、、というケースに対応するべく、さくっとCSSで非表示にしてみました。

.s4-recentchanges
{
  display : none;
}

うーん、CSSって便利ー。