Hatena::ブログ(Diary)

IIJIMASの日記 RSSフィード

2014-10-30

サイトコレクションのURLに関して

SharePoint 2013のサイトコレクションのURLには、

"/"

のほかに、

"/sites/(sc)/"

という形式のものがあります。

"/"

のサイトコレクションのコンテンツエディターWebパーツで、サイトコレクションのスタイルライブラリに配置したJSファイルとCSSファイルを

<script type="text/javascript" src="/Style%20Library/test1.js" />
<link type="text/css" rel="stylesheet" href="/Style%20Library/test2.css" />

という風に記述して読み込んで、後続コードで、これらファイルに定義されている関数を呼び出したりしていました。このコンテンツエディターWebパーツをエクスポートして、後者の形式の環境にアップロードして確認したところ、動きませんでした。。理由は、URLのパスが無効だったからです。以下のように先頭に「/sites/(sc)」を付けて書き換えればOKです。もちろん絶対URLでもOKです。

<script type="text/javascript" src="/sites/(sc)/Style%20Library/test1.js" />
<link type="text/css" rel="stylesheet" href="/sites/(sc)/Style%20Library/test2.css" />

これで解決しましたが、なんかコードの携帯性が悪い気がしたので、どっちの形式でもOKな方法はないか考えてみました。

まず、SharePointのページで、URLがどっちの形式なのかを判別できる

_spPageContextInfo.webServerRelativeUrl

ものがあることがわかりました。(まあ、location.hrefでもsitesがあるかどうかでわかりますが。。)

この部分のパスが取れるようなので、これを使って、JSCSSを動的にロードしようと思いました。

ただし、ここに入っているのは、

サイトコレクションURLwebServerRelativeUrl
//
/sites/(sc)//sites/(sc)

なので、単純に「_spPageContextInfo.webServerRelativeUrl+"/Style%20Library/xxx"」または「_spPageContextInfo.webServerRelativeUrl+"Style%20Library/xxx"」としてしまうと、どちらかの形式のURLのサイトで動きません。

というわけでとりあえず以下のようなコードを考えてみました。これをコンテンツエディターWebパーツのソースの最初の<script type="text/javascript">-</script>の中に書きます。

(function(){
function LoadJS(src) {
    var s = document.createElement("script");
    s.setAttribute("type", "text/javascript");
    s.src = src;
    document.head.appendChild(s);
}

function LoadCSS(href) {
    var l = document.createElement("link");
    l.setAttribute("type", "text/css");
    l.setAttribute("rel", "stylesheet");
    l.setAttribute("href", href);
    document.head.appendChild(l);
}

function LoadJSWithSiteUrl(src) {
    var basePath=_spPageContextInfo.webServerRelativeUrl;
    LoadJS(((basePath!="/") ? basePath :"")+src);
}

function LoadCSSWithSiteUrl(href) {
    var basePath=_spPageContextInfo.webServerRelativeUrl;
    LoadCSS(((basePath!="/") ? basePath :"")+href);
}

LoadJSWithSiteUrl("/Style%20Library/test1.js");
LoadCSSWithSiteUrl("/Style%20Library/test2.css");
})();

scriptタグやlinkタグの代わりとしては、長すぎる(やることに対してコードが大袈裟過ぎる)ので実際の業務ではとりあえず使用しないつもりです。(実際の配置環境に応じて書き換えるのが現実的)

もっといい方法がありましたらぜひ教えてください。

2014-10-29

テキストファイルの出力

JavaScriptだけでもテキストファイル(UTF-8)の出力ってできるんですね。

ただし以下は、Blobオブジェクトを使っています。なのでHTML5 File APIを使用してます。たとえば、IE9以下は未対応なので、仕事のコードでは使用し(でき)ませんでした。。

function OutputText(text, fileName) {
    var b = new Blob(["\uFEFF", text]);
    if (navigator.msSaveBlob) {
        navigator.msSaveOrOpenBlob(b, fileName);
    } else {
        var a = document.createElement('a');
        a.href = URL.createObjectURL(b);
        a.setAttribute('download', fileName);
        a.dispatchEvent(new CustomEvent('click'));
    }
}

たとえばHTMLフォームのボタンから以下のように呼びだします。

<input type="button" value="exec" onclick="OutputText('こんにちは!', 'output.txt');" />

とくに、意味はないですが一行のJavaScriptブックマークレットも作ってみました。お気に入りのURLに入れて保存して、クリックすると「こんにちは!」という文字列のファイルがDLできるだけです。

javascript:b=new%20Blob(["\uFEFF","\u3053\u3093\u306B\u3061\u306F\uFF01"]);f="test.txt";if(navigator.msSaveBlob){navigator.msSaveOrOpenBlob(b,f);}else{a=document.createElement('a');a.href=URL.createObjectURL(b);a.setAttribute('download',f);a.dispatchEvent(new%20CustomEvent('click'));}

2014-10-18

配列で順序を保ったまま集合算をする

JSの配列で集合算を考えてみた - IIJIMASの日記」の続きです。

配列が2つ与えられたとき、重複を省きつつ、和集合、共通部分、差集合などを表す配列を計算するアルゴリズムで、ループ回数をなるべく少なくしたものを考えます。

以前のエントリのやり方だと、順序が保存されないので、今回は順序が保存されるのを考えてみました。とりあえず考えたアルゴリズムでは対称差(XOR)だけ余計にループが必要となってしまいました。それ以外は、ループは2回で済みます。冒頭部分の演算によって引数の順番を交換しているところは、再帰で書いた方がすっきりするかもしれませんがわかりにくくなると思います。

//演算の種類を表す定数。値に意味はなし、互いに異なればOK
MathSet.Operator.Union = 1;
MathSet.Operator.Intersection = 2;
MathSet.Operator.DiffLR = 3;
MathSet.Operator.DiffRL = 4;
MathSet.Operator.SymmDiff = 5;

MathSet.OperationWithOrdering = function (arrayA, arrayB, operator) {
    var set = {};
    switch (operator) {
        case MathSet.Operator.DiffLR:
        case MathSet.Operator.Intersection:
        case MathSet.Operator.SymmDiff:
            a1 = arrayB;
            a2 = arrayA;
            break;
        case MathSet.Operator.Union:
        case MathSet.Operator.DiffRL: 
        default:
            a1 = arrayA;
            a2 = arrayB;
    }
    var val;
    var newArray = [];
    for (var i = 0; i < a1.length; i++) {
        val = a1[i];
        if (set[val] == null) {
            set[val] = 1;
            if (operator == MathSet.Operator.Union) {
                newArray.push(val);
            }
        }
    }
    for (var j = 0; j < a2.length; j++) {
        val = a2[j];
        if (set[val] == 1) {
            set[val] = 3;
            if (operator == MathSet.Operator.Intersection) {
                newArray.push(val);
            }
        } else if (set[val] == null) {
            set[val] = 2;
            if (operator != MathSet.Operator.Intersection) {
                newArray.push(val);
            }
        }
    }
    if (operator == MathSet.Operator.SymmDiff) {
        for (var i = 0; i < a1.length; i++) {
            val = a1[i];
            if (set[val] ==1) {
                set[val] = 4;
                newArray.push(val);
            }
        }
    }
    return newArray;
};

以下のように使用します。(exec()をボタンのonclickなどで指定して、クリックで実行します。)

function exec() {
    result = "";
    result += "arrayA:\t"; result += A; result += "\r\n";
    result += "arrayB:\t"; result += B; result += "\r\n";
    result += "\r\n";
    result += "A∪B:\t"; result += MathSet.OperationWithOrdering(A, B, MathSet.Operator.Union); result += "\r\n";
    result += "A∩B:\t"; result += MathSet.OperationWithOrdering(A, B, MathSet.Operator.Intersection); result += "\r\n";
    result += "A-B:\t"; result += MathSet.OperationWithOrdering(A, B, MathSet.Operator.DiffLR); result += "\r\n";
    result += "B-A:\t"; result += MathSet.OperationWithOrdering(A, B, MathSet.Operator.DiffRL); result += "\r\n";
    result += "XOR:\t"; result += MathSet.OperationWithOrdering(A, B, MathSet.Operator.SymmDiff); result += "\r\n";
    alert(result);
}

結果:

arrayA:	100,3,1,2,3,2,1
arrayB:	8,12,6,2,3,5,6,3,2

A∪B:	100,3,1,2,8,12,6,5
A∩B:	3,2
A-B:	100,1
B-A:	8,12,6,5
XOR:	100,1,8,12,6,5

結果(順序が保存される)

f:id:IIJIMAS:20141018092906p:image

同じ入力で以前のエントリアルゴリズム(順序が保存されない)

f:id:IIJIMAS:20141018093113p:image

もっと効率が良くて、短いもの、解かりやすいものを考えたならばぜひ教えてください。

2014-10-12

JSの配列で集合算を考えてみた

どんなプログラミングでも基本的な配列の操作において、集合算を実装したいということはあるかと思います。言語仕様やライブラリに実装されていればそれを使えばよいですが、そうでない場合に自分で実装せざるをえないなくなったり、興味や頭の体操で実装してみたくなることもあるかと思います。

新しいJavaScriptではSetオブジェクトが定義されているようですが、(→[Link]「ECMAScript Language Specification ECMA-262 6th Edition – DRAFT」)こちらにもとりあえず集合算は見当たりません。

前からある配列(Arrayオブジェクト)で集合算をするロジックを考えてみました。具体的には2つの配列から新しい結果の配列を返す関数を考えてみました。Arrayは重複を許しますので、重複を排除しつつ計算します。和集合は全ての要素だから簡単ですが、共通部分は割と複雑です。ひとまとめにして、とりあえず以下のような関数を実装してみました。

配列Aと配列Bを集合A,Bとみなして(重複は排除)、全体の和集合A∪BをA-BとA∩BとB-Aに分けて、演算の種類に応じて各集合から要素を新しい配列に詰めるかどうかを指定します。

var MathSet;
MathSet = {};
MathSet.Operator = {};
MathSet.Operator.Union = [1,1,1];
MathSet.Operator.Intersection = [0,0,1];
MathSet.Operator.DiffLR = [1,0,0];
MathSet.Operator.DiffRL = [0,1,0];
MathSet.Operator.SymmDiff = [1,1,0];
MathSet.Operation = function(arrayA, arrayB, operator) {
    var ptnA = [operator[0]];
    var ptnB = [operator[1]];
    var ptnAB = [operator[2]];
    var set = {};
    for (var i = 0; i < arrayA.length; i++) {
        set[arrayA[i]] = ptnA;
    }
    for (var j = 0; j < arrayB.length; j++) {
        set[arrayB[j]] = (set[arrayB[j]] && set[arrayB[j]] != ptnB) ? ptnAB : ptnB;
    }
    var newArr = [];
    for (var v in set) {
        if (set[v][0] == 1) {
            newArr.push(v);
        }
    }
    return newArr;
};

以下のように使用します。(exec()をボタンのonclickなどで指定して、クリックで実行します。)

function exec(){
var A = [1,2,3,3,1,2];
var B = [2,3,5,6,3,6,8];

result="";
result+="arrayA:\t";result+=A;result+="\r\n";
result+="arrayB:\t";result+=B;result+="\r\n";
result+="\r\n";
result+="A∪B:\t";result+=MathSet.Operation(A, B, MathSet.Operator.Union);result+="\r\n";
result+="A∩B:\t";result+=MathSet.Operation(A, B, MathSet.Operator.Intersection);result+="\r\n";
result+="A-B:\t";result+=MathSet.Operation(A, B, MathSet.Operator.DiffLR);result+="\r\n";
result+="B-A:\t";result+=MathSet.Operation(A, B, MathSet.Operator.DiffRL);result+="\r\n";
result+="XOR:\t";result+=MathSet.Operation(A, B, MathSet.Operator.SymmDiff);result+="\r\n";
alert(result);
}

結果:

arrayA:	1,2,3,3,1,2
arrayB:	2,3,5,6,3,6,8

A∪B:	1,2,3,5,6,8
A∩B:	2,3
A-B:	1
B-A:	5,6,8
XOR:	1,5,6,8

f:id:IIJIMAS:20141012120522p:image

実行例では、数字要素のサンプルとしましたが、文字列要素などでももちろん実行可能です。

もっと効率が良くて、短いもの、解かりやすいものを考えたならばぜひ教えてください。

2014-10-10

関数を動的に書き換えてみた

Webアプリサーバー側コードが生成するクライアント側コードのJavaScriptで、ある関数の一部だけ、書き換えたいことがあるとします。

その関数は、非nativeで定義を閲覧可能で全体を書き換える程でもない(書き換えたくはない)とします。その関数の呼び出しの前後へのコード追加では用が足りなくて、その関数の中の一部だけを書き換えたいとします。

関数の定義コードの文字列を置き換える関数を考えてみました。

以下のreplaceFuncは、第1引数が書き換えたい関数で、第2引数は元のコードを新しいコードに置き換えた関数を返す関数です。

function replaceFunc(func, replaceMethod){
	var funcScript = func.toString();
	var funcMatches = funcScript.match(/function *([^\(]*)\((.*)\)( *)/);
	var funcSignature = funcMatches[2];
	var funcBody = RegExp.rightContext;
	var newScript ="var f = function("+funcSignature+")"+replaceMethod(funcBody)+";";
	eval(newScript);
	return f;
}

使用例(onloadやonclickでexec()を呼び出します):

//書き換えたい関数(使用例は短いコードだが、意味のある複数行でもよい)
myfunc=function   (  x  ,   y   )     {
	return x   + y;
};
//書き換えたい関数の定義は、以下の形式でもよい
function myfunc0(  x  ,   y   ){return x +y;}
//使用例
function exec(){
	var result="";
	result+=myfunc.toString(); result+="\r\n";
	result+="myfunc(3,5)="+myfunc(3,5); result+="\r\n";
	
	myfunc = replaceFunc(myfunc,
		function(src){return src.replace(/x\s*\+\s*y/,"x*y");});
		
	result+=myfunc.toString(); result+="\r\n";
	result+="myfunc(3,5)="+myfunc(3,5); result+="\r\n";
	alert(result);
}

結果:

function   (  x  ,   y   )     {
	return x   + y;
}
myfunc(3,5)=8
function(  x  ,   y   ){
	return x*y;
}
myfunc(3,5)=15

IEでの実行結果 Google Chromeでの実行結果
f:id:IIJIMAS:20141010013014p:image:w320 f:id:IIJIMAS:20141010013627p:image:w320