SharePointのリストフォームに採番機能をJavaScriptで実装する

この世にはようかんぱんなるものがあることを知り気になって仕方ないのですが、そんなことはどうでもいいですね。

さて、だいぶ間が空いてしまいましたが久々の更新です!
いぜん、ページにビューカウンタをつける方法をポストしましたが、意外とよく読まれているようなので、気を良くして応用編(?)です。

SharePointのリストアイテムに、システム的に振られるIDではなく、独自ルールで採番したカスタムIDを振りたい!という要望はよくありますよね。
そこで、ページビューカウンタと同じやり方で、採番機能を実現してみました。
新規フォームで見るとこんな感じ。
20151217

実装方法です。
事前に「counter」という名前の採番用カスタムリストを同じサイト内に作成しておきます。
採番用カウンタは、Titleフィールドをそのまま使います。
新規フォーム作成時に、採番用リストから番号を取得し採番フィールドに表示、保存時のアクションでインクリメントした番号を採番用リストに返す仕組みです。
なので、新規フォームをキャンセルした場合は、インクリメントされません。

以下、ソースコードです。
こちらをNewForm.aspxにコンテンツエディタwebパーツで埋め込んでください。

SPServicesjQuery使ってます。

さらにちょっとした小技として、

21行目:カスタムIDの数値部分を桁固定0埋めで表示
23行目:採番フィールドを読み取り専用にする
24行目:採番フィールドがフォームの最初のフィールドでもフォーカスを当てない

などを盛り込んでおります。

なお、EditForm.aspxには以下を埋め込んでおきます。

検証環境は2010ですが、2007、2013でも動くと思います。
参考にしていただけるとうれしいです!

広告

SharePointのwikiページにページビューカウンタをつけてみた

気が付いたら、今年も残すところあと数日・・・早いものですね。
私らしくどうでもいいネタで今年を締めくくりたいと思います。

もうすっかり前世紀の遺物となってしまった感のあるアクセスカウンターですが、サイトのトップページやお知らせページなんかの場合てっとりばやくどのくらいアクセスがあるのか知りたいって時もあるんじゃないかと思います。

実装するやり方はいろいろあるかと思いますが、ここではアクセスカウントを格納しておくカスタムリストを一つつくっておいて、ページロードの時にJavaScriptでカウンタをインクリメントするようにしてみました。
試した環境はSharePoint2010ですが、2013でもたぶんいけると思います。

下準備:
①サイト内にカスタムリストを作成し、countという一行テキスト列を追加しておきます。
今回は「test」という名前でカスタムリストを作成しました。
②作成したリストに新規アイテムを一つ作成し、countには「0」を入れておきます。

以上で準備は終わりです。
次に、カウンタをつけたいページにコンテンツエディタwebパーツを追加し、そのHTMLソースに以下のコードを記述します。

<div id="pageView"></div>
<script src="http://code.jquery.com/jquery-1.11.1.min.js" type="text/javascript"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/2014.01/jquery.SPServices.min.js" type="text/javascript"></script>
<script type="text/javascript">
(function (){

    var itemId;
    var count;

    $().SPServices({
        operation: "GetListItems",
        async: false,
        listName: 'test',
        completefunc: function (xData, Status) {
            $(xData.responseXML).SPFilterNode("z:row").each(function () {
                itemId= $(this).attr("ows_ID");
                count = Number($(this).attr("ows_count"));
            });
        }
    });

    count++;
    count = String(count);

    $().SPServices({
        operation: "UpdateListItems",
        async: false,
        batchCmd: "Update",
        listName: "test",
        valuepairs: [["count", count]],
        ID: itemId,
        completefunc: function(xData, Status) {
          //
        }
    });

    $('#pageView').empty();
    $('#pageView').append('<p>' + count + ' views</p>' );

})();
</script>

以上でOKです!
こんな感じで控え目にページビューが表示されます。
20141226

ちなみに、上記サンプルコードは、多数アクセスがあったときの排他制御とか、複数ビューカウンタをつけたい場合とか、自分のアクセスや編集モードにしたときのページロードを排除するとかは一切考慮しておりません。
何かのヒントになればうれしいです。

それではみなさま、よいお年を!

SharePointの予定表をjQueryUIのDatePickerで表示してみた

SharePoint2013の検証環境が壊れてしまいショック死しておりましたが、ようやく生き返りました。サーバーも早く息を吹き返してほしいものです・・・

さて、SharePointのチームサイトでは、予定表はよく使う機能の一つだと思います。
ただ、webパーツとしてサイトのトップページに表示させたい場合、標準のwebパーツだとはっきり言ってデカいです。
で、小さくしたい!しかもかっちょよく!と常々思っていました。

CSSで何とかするやり方は

予定表 Web パーツのカレンダー表示をコンパクトにしてみた | idea.toString;

とかあって、これでいいよねこれで・・・といったんは思ったんですが、ついでにイベント一覧も表示したりできるといいよね、ということで自作してみることにしました。

UIはjQuery UIのDatePickerかっこいいから使いたい、そこでググってみたところCodePlexによさそうなソリューションを発見。
http://jqcalendarpart.codeplex.com/
おお~、これこれ!と思ったのですが、一つ問題が・・・
Limitations:
The web part will not work if you try to navigate to the next month or to the previous month. Please wait as I’m working on a fix to highlight all the dates which are having event registered when a user is navigating to the next month.

とあるように、月を切り替えるとイベントが表示されなくなっちゃうという・・・うむむむ・・・これでは実用に耐えない・・・

というので、上のコードを元にがんばって動くものを作ってみました。
上記のCodePlexのソリューションとは違い、ホバーではなく下に表示月のイベントを表示するようにしています。
20140722

なお、月を切り替えてもちゃんとイベントの日付を色付けするには、どうやら下記のようにonChangeイベントでやりたい処理をsetTimeoutで遅延させるのがキモのようです。
(参考にさせていただいた記事:書いて忘れる: jQuery:UI datepickerの週末に色をつける.)

    $(".divDatePicker").datepicker({
        onChangeMonthYear: function (year, month, inst) { //表示月変更 
            setTimeout(function () { syncCalendar(year, month, selectDate.getDate()); }, 100);
        },
        onSelect: function (dateText, inst) { //選択日変更
            dispDayEvent(dateText);
        }
    });

完全なソースコードはGistに置いてます。ご自由におとりください。
HTML←これをコンテンツエディタ webパーツでページに埋め込んでください。
https://gist.github.com/marineko/6baa039110521f3b043a
JavaScript
https://gist.github.com/marineko/63c7a386786ea7d6d2de
CSS
https://gist.github.com/marineko/469a84fd8ab882d41e76
※SharePoint2010、2013両方で動作確認済みです。

依存関係のあるファイルです。上記ソースコードと合わせ、以下もページに読み込んでおいてください。
jQuery
jQuery UI
XDate
SPServices

なお、jQuery UIのテーマは、フラットなUIテーマの作れる以下のwebサービスで作りました。便利な世の中になりましたねー。

Build jQuery UI themes with jQUIT Builder

なお、コンテンツエディタを使ってページにスクリプトなどを置くやり方は、以下のポストをご参考に・・・
JavaScriptなど外部ファイルをページに読み込む
予定表のフォームで会議ワークスペース列を非表示にする

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出てきませんでしたね?!(汗)。次回はきっと登場します・・・

編集フォームでコンテンツタイプのドロップダウンを非表示にする

SharePoint 2010のお話。
ちょっとしたワークフローをカスタムリストで作成したときのことです。

申請部門から管理部門へ申請が渡ったとき、管理部門でリストのコンテンツタイプを切り替えて、管理用のフィールドを表示させる仕様にしたのですが、そのまま何もしないと、申請部門の人がフォームを編集状態にしたときにもコンテンツタイプ切り替え用のドロップダウンが表示されてしまいます。

contentType

さわんなよー!という運用でもよかったのですが、できるものなら隠したい。というのでJavaScriptでがんばってみました。
管理部門グループ以外のメンバがフォームを編集状態にすると非表示になる仕組みです。

あっと、以下のコードjQueryと私の愛してやまないSPServices使ってますので・・・

var currentuser = $().SPServices.SPGetCurrentUser();
var ct_str;
if (_spPageContextInfo.currentLanguage == "1041") {
    ct_str = "コンテンツ タイプ";
} else{
    ct_str = "Content Type";
};

var groupname = ”管理部門グループ名”;
$().SPServices({
    operation: "GetGroupCollectionFromUser",
    userLoginName: currentuser,
    async: false,
    completefunc: function (xData, Status) {
        if ($(xData.responseXML).find("Group[Name=\'" + groupname + "\']").length == 0) {
                $("tr:has(select[title=\'" + ct_str + "\'])").not("tr:has(tr)").hide();
        }
    }
});

P.S.
こちらに参加してみようかと・・・
SharePointの入門~活用~Enterprise までの濃い1か月間の勉強会Monthly第一弾

いい年して人見知りなので、その場にフリーズしてそうですが。

列に現在のユーザー名を表示【2010】

SharePoint2010のカスタマイズです。

カスタムリストで、ユーザーとグループ列に既定で現在のユーザー名を表示したい、というシチュエーションは多いですよね。
で、その方法もさんざん既出なんですが、
(たとえば
SharePoint Maniacs » JavaScriptを利用して、列に既定値を設定する(3).
とか)

実際書こうとすると、また一からネットで調べなおしたりいろいろと面倒なので、自分用にメモ。

・・・っと、以下のコードjQuerySPservices使ってますので、ダウンロードして自分のサイトの適当なリポジトリに入れて<script>タグで先に読み込んでおいてください。(CDNから読み込んでもいいです、もちろん)

var currentuser = $().SPServices.SPGetCurrentUser();

var lcid = _spPageContextInfo.currentLanguage;
var pp_str;
var img_str;

if (lcid == "1041") {
    pp_str = "ユーザー選択ウィンドウ";
    img_str = "名前の確認";
} else {
    pp_str = "People Picker";
    img_str = "Check Names";
};

var columnName = 'Requestor Name';
var searchText = RegExp("FieldName=\"" + columnName + "\"", "gi");

$("td.ms-formbody").each(function () {

    if (searchText.test($(this).html())) {
        $(this).find("div[Title=\'" + pp_str + "\']").html(currentuser);
        $(this).find("textarea[title=\'" + pp_str + "\']").val(currentuser);
        $(this).find("img[title=\'" + img_str + "\']").trigger("click");
    }

});

以上のコードをNewForm.aspxにコンテンツ エディタ webパーツで埋め込みます。

で、以上は2010のやり方。
2013ではうまくいきません・・・2013でナントカするやり方はまた。