[C#]クラスのプロパティに数値セット時、範囲外データの処理をシンプルに扱う

クラスのプロパティに数値をセットする際、値として有効な範囲が決まっている場合があります。

範囲外の値が指定された場合は例外を投げるというのも1つの実装ですが、例外ではなく上下限値が指定されたとみなしたい場合はどうやって実装するとシンプルになるでしょうか?
例えば0~100の値が有効値で、負の数が指定された場合は0、100以上の値が指定された場合は100とみなしたい場合、以下のように記述するとすっきりします。

private int _Value;
public int Value { 
    set {
        // 指定値をセット(但し有効範囲は,0~100のみ)
        this._Value = Math.Min( 100, Math.Max( 0, value ) );
    }
    get {
        return this._Value;
    }
}



0未満の値が入ったときに、0とみなす処理を以下のように記述しています。
※プロパティのsetに記述した処理内で、valueは入力値を意味します

Math.Max( 0, value )



同様に上限値に関しては,Min関数で制限しています。

[C#]関数の呼び出し元のメソッド名を知る方法

下記のようにStackTraceクラスを使用することで、関数の呼び出し元を知ることが出来ます。

通常、関数は呼び出し元が誰であるかに依存せずに動作する必要がありますが、ログ出力を自作する場合等では便利かもしれません。

private static string GetCallerMethodName() {
    System.Diagnostics.StackTrace stack  = new System.Diagnostics.StackTrace(false);
    return stack.GetFrame(2).GetMethod().Name;
}

[C#]Leaky Bucketアルゴリズムを実装する

AmazonのAPIでは呼び出し制限にリーキーバケットアルゴリズムを採用しています。今回、クライアント側で呼び出し回数が超過しないようチェックするため、リーキーバケットのアルゴリズムを実装を実装したクラスを作成しました。
内部で、BackGroundWorkerオブジェクトを生成し、指定された周期で、現在の値を再計算しています。


使い方は以下のような感じです。

オブジェクトの生成

LeakyBucket quota = new LeakyBucket();




値の初期化。
下記の例では、現在値が0、最大値が10で2000mSec(2秒)ごとに現在値を減算します。

// 初期値のセット
quota.Value       = 0;
quota.MaxQuota    = 10;
quota.RestoreRate = 2000;



値の加算。
AmazonのAPI呼び出しを行うたびにインクリメントします。
加算したquota.Valueの値は、RestoreRateの周期で勝手に1づつ減算されていきます。

private boolean CallRequest() {
	if ( quota.MaxQuota <= quota.Value ) {
		return false;;
	}
 
	quota.Value++;
 
	// ここでリクエストの呼び出し処理を行う
 
	return true;
}




ちゃんとしたライブラリとして、Leaky Bucketアルゴリズムを実装するなら、本当はBackgroundWorkerなんか使わずに各プロパティをセット・参照したタイミングでquota値を再計算した方が良いです。
理由は、大量にオブジェクトを生成したとき、周期的なチェック処理の負荷が無視できなくなるのと、Sleepのタイミングによっては厳密には正しい値が取れず、実際のquotaより1つ大きい値を取得してしまうリスクがあるからです。

だけど今回は1つだけしたカウンタが不要で、かつ概算値が取れれば十分なので、これで良しということにしておきます。


呼び出し側のサンプル用プログラム(exeとソースの両方あります)。
LeakyBucketTest.zip


ダウンロード
LeakyBucket.zip

ソーラー発電システムを自作する場合のコスト試算

ソーラー発電システムの自作に興味があったので、どの程度費用が掛かるかシミュレーションしてみました(まだ実際に購入・組み立ては行ってないです)。

とりあえず、欲しい電力として「ノートPCを5時間動かせるようにする」程度の規模で考えてみました。

必要な蓄電容量


2010年発売あたりのノートPCだと大体40Wぐらいなので、必要な供給電力の目安は200Wとなります。

40W/h x 5hr = 200W



また毎日200W分の電力消費を節約できると、ざっくりいって月間140円の電気代に相当します。



必要な部品

ソーラー発電の仕組みを作るには、何が要るかですが…説明するのがめんどうなので、以下のページを見てください。
どっちも初心者にも非常に分かりやすい内容です

ざっくり概念を理解するにはこちら → Bライフ研究所 » ソーラー発電
こっちの方は、詳しく説明されてます → 蓄電システム.com



何が必要なのかを把握できたら、各パーツの必要スペックと費用を検討します。

ソーラーパネル

余裕を見て、必要パワーに対して1.5倍分である300W分を確保します。
(変換ロス、晴天以外で発電できない場合の考慮)
3時間発電できるとすると… 300W / 3hr = 100Wなので、100Wのパネルが必要となります。

ただ、いきなり100Wのパネルにで作って失敗するとショックが大きいので、手始めに50Wを1枚だけ買って練習してみることにします。(50Wでうまくいったら,後でもう一個買い増して、直列でつなぐので)

参考商品
    http://www.chikuden-sys.com/category/solar-panel_list.asp?id=11
 
    9,800円 x 2個 = 19,600円





バッテリー

入手が容易な12Vのバイク用バッテリーを使います
必要な蓄電量は200Wだったので、12Vということは、200W / 12V = 17Ahの容量が必要となります

参考商品
    http://akizukidenshi.com/catalog/g/gB-00658/
 
    4,350円
    5時間率17Ah





コントローラー


以下の条件を満たすものが必要です。

100Wの入力に対応しているもの
12Vで出力できるもの
8.5Aまでコントロールできるもの(100W / 12V = 8.3A)

参考商品
    http://akizukidenshi.com/catalog/g/gM-02824/
 
    太陽電池充電コントローラーです、公称電圧12V、24Vの鉛蓄電池用(自動認識)
    太陽電池とバッテリーをつなぐだけ(インジケータ用LCD内蔵)
    ■取り扱い可能最大電流:10A(逆流防止機構付)
    ■自己消費電流:4mA以下
    ■寸法:92×93×38mm
    ■重量:168g
 
    10,000円
    本体6000円+ディスプレイが4000円




インバータ

入力12Vで、起動時に負荷か掛かるのを考慮して100Wまで対応のものをチョイスします。
出力は正弦波のものと擬似正弦波のものがありますが、ノートPCやケータイの充電程度なら疑似正弦波で十分です。

参考価格
    http://akizukidenshi.com/catalog/g/gM-00679/
 
    ◆入力:DC12V
    ◆出力:AC100V
    ◆周波数:55Hz
    ◆連続最大電力:150W(@力率100%)
    ◆ピーク電力:300W、1サイクル
    ◆出力波形:疑似正弦波(実測波形)
    ◆無負荷時直流側電流:0.2A(メーカー公表値)
    ◆効率:90%(メーカー公表値)
    ◆サイズ:155x70x40 約500g
    ◆過負荷保護・短絡保護
    ◆バッテリー電圧低下保護(10.5V)
    ◆内蔵ヒューズ:20A(付属)交換用:P-00748
 
    1,700円




コスト試算


パネル1つの場合

    費用合計
        9,800 + 4,350 + 10,000 + 1,700
            -> 25,850
    浮く電気代
        140円/月
 
    25,850 / 140 ≒ 180ヶ月でペイする 
                 ≒ 15年ぐらい



パネルをもうひとつ足すと…

    費用合計
        9,800*2 + 4,350 + 10,000 + 1,700
            -> 33,850
    浮く電気代
        280円/月
 
    25,850 / 140 ≒ 120ヶ月でペイする 
                 ≒ 10年ぐらい



家の屋根に設置するような大きなソーラーパネルシステムとかと違って補助金が出ないので、その辺を考慮すると、まぁこんなもんかな…というレベルでした。
金銭面だけ考えるとあまりメリットは無いですが、趣味の遊び+勉強としては良さげな感じです。

[C#]ウィンドウを画面右下に表示させる方法

以下のコードをFormのLoadイベントに記述すると、画面がウィンドウの右下に表示されます。

// ウィンドウを画面右下に表示させる
int left = Screen.PrimaryScreen.WorkingArea.Width  - this.Width;
int top  = Screen.PrimaryScreen.WorkingArea.Height - this.Height;
DesktopBounds = new Rectangle( left, top, this.Width,this.Height );



左下や右上に表示したい場合は、Rectangleの引数をそれぞれleft=0, top=0にすればOKです。

[C#]タスクトレイアイコンにバルーンヘルプを表示させる共通関数

たかだかメッセージを表示させるために何行もコードを書きたくない…
というわけで、汎用のラッパ関数を作成しました。

使い方

// 3秒間エラーメッセージを表示させる。
showBloonHelp( TaskTrayIcon, ToolTipIcon.Error, "エラーです。", 3000 );
 
// 1秒間情報メッセージを表示させる。
showBloonHelp( TaskTrayIcon, ToolTipIcon.Info, "パターンファイルが更新されました。", 1000 );




ソース

//*********************************************************************
/// <summary> タスクトレイアイコンにバルーンヘルプを表示させる
/// </summary>
/// <param name="targetIcon">表示先のアイコン</param>
/// <param name="level">  メッセージレベル
///							ToolTipIcon.Info   : 情報
///							ToolTipIcon.Warning : 警告
///							ToolTipIcon.Error  : エラー
/// </param>
/// <param name="msg">		表示させるメッセージ</param>
/// <param name="timeout">	バルーンの表示時間</param>
//*********************************************************************
public void showBloonHelp( NotifyIcon targetIcon, ToolTipIcon level, string msg, int timeout ) {
	string title;
	switch ( level ) {
		case ToolTipIcon.Info:
			title = "情報";
			break;
		case ToolTipIcon.Warning:
			title = "警告";
			break;
		case ToolTipIcon.Error:
			title = "エラー";
			break;
		default:
			title ="";
			break;
	}
 
	targetIcon.BalloonTipIcon  = level;
	targetIcon.BalloonTipTitle = title;
	targetIcon.BalloonTipText  = msg;
	targetIcon.ShowBalloonTip( timeout );
}

[C#]SoundPlayerクラスを使用して、1行でwav再生を行う。

下記の1行で、wavファイルの再生が可能。

(new System.Media.SoundPlayer("test.wav")).Play();



同期再生の場合は、PlaySync()を使います。

ちなみに、SoundPlayerではmp3の再生や再生音量の調整は出来ません。

[C#,日付]ISO8601形式の文字列と、DateTime型の相互変換を行う方法

C#で時刻情報を管理する場合、DateTime型がよく利用されます。
一方、ログファイルなどで日付を文字列として表記する際、ISO8601形式で表現することがあります。


このISO8601形式というのは日付と時刻の表記に関する国際規格で、以下のようなフォーマットです。

2011-02-26T23:59:59Z


※厳密に言うと、規格上は他にも曜日表記を付け加えるなどのバリエーションがありますが、本記事では最もポピュラーな上記の書式のみを取り扱います。



この、ISO8601形式の文字列と、C#で用意されているDateTime型は、
以下のコードで相互変換を行えます。

DateTime dtData;
string strData;
 
// DateTime型→ISO8601形式の文字列
string strData = dtData.ToString( "yyyy-MM-dd'T'HH:mm:ss'Z'" );
 
// ISO8601形式の文字列→DateTime型
dtData = DateTime.Parse( strData,  null, System.Globalization.DateTimeStyles.RoundtripKind );




ちなみに、このフォーマットは国際規格上はISO8601ですが、JIS規格では”JIS X 0301″として定義されています(内容は同じです)。

[C#]DataGridViewで、行ヘッダにある三角マーク”▲”を非表示にする


DataGridView使用時に行ヘッダを表示させると、選択行に三角のマークが表示されます。
表示専用のグリッドなどで、上記マークを非表示にしたい場合は以下の作業を行います。



DataGridViewが標準で使用しているRowHeaderCellクラスが表示させているので、まずはこれを継承したクラスを作成します。
最後の引数で指定されたpaintPartsを弄って、状態表示のアイコンを非表示にしています。

//---------------------------------------------
// 三角マーク非表示版の、DataGridView行ヘッダー
//---------------------------------------------
class DgvRowHeaderNoTriangle : DataGridViewRowHeaderCell {
    protected override void Paint(  Graphics                        graphics,
                                    Rectangle                       clipBounds,
                                    Rectangle                       cellBounds,
                                    int                             rowIndex,
                                    DataGridViewElementStates       cellState,
                                    Object                          value,
                                    Object                          formattedValue,
                                    string                          errorText,
                                    DataGridViewCellStyle           cellStyle,
                                    DataGridViewAdvancedBorderStyle advancedBorderStyle,
                                    DataGridViewPaintParts          paintParts ) {
        paintParts &= ~DataGridViewPaintParts.ContentBackground;
        base.Paint( graphics, clipBounds, cellBounds, rowIndex, cellState, value, 
                    formattedValue, errorText, cellStyle, advancedBorderStyle,
                    paintParts );       
    }
};





上記クラスを定義後、画面のロード処理でグリッドのHeaderCellクラスを変更します。

private void Form1_Load( object sender, EventArgs e ) {
    DataGridView1.RowTemplate.HeaderCell = new DgvRowHeaderNoTriangle();
}




プログラム内で動的に、表示を元に戻したい場合は上記のプロパティをnullにすればよいです。

DataGridView1.RowTemplate.HeaderCell = null;

[C#]アプリの設定画面で入力した内容をapp.configで管理する

アプリケーションでは通常、「ツール→オプション」等のメニューで、プログラムの動作内容をユーザがカスタマイズできることが多いです。

本記事では、C#のプログラムにて、ユーザが設定画面で入力した情報をapp.configで管理する手順を説明します。


1.設定画面を作ります
下記は、設定画面のサンプル画像です。Formを使って普通に作ればOKです。



2.設定画面のOK,キャンセルボタンに対してハンドラを定義します

//*********************************************************************
/// <summary> OKがクリックされたときのハンドラ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
//*********************************************************************
private void BtnOk_Click( object sender, EventArgs e ) {
	DialogResult = DialogResult.OK;
	Close();
}
 
//*********************************************************************
/// <summary> キャンセルがクリックされたときのハンドラ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
//*********************************************************************
private void BtnCancel_Click( object sender, EventArgs e ) {
	DialogResult = DialogResult.Cancel;
	Close();
}




3.app.configで管理したい設定項目欄のApplicationSettings->PropertyBindingを選択し、右にある”…”をクリックします




4.管理したいプロパティを選択し、新規をクリックします


5.Nameに管理項目のキーを入力し、OKをクリックします
(キーはプログラム内で一意な必要があります)




6.設定画面の呼出元を、以下のような感じで作ります
設定内容を保存するには、プログラムから明示的にsave()をコールする必要があります。

//*********************************************************************
/// <summary> 設定ボタンクリック時のハンドラ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
//*********************************************************************
private void MenuItem_Setting_Click( object sender, EventArgs e ) {
 
	FrmSetting form = new FrmSetting();
 
	DialogResult result = form.ShowDialog();
	if ( !result.Equals( DialogResult.OK ) ) {
		// 設定内容を読み込みなおす
		Properties.Settings.Default.Reload();
		return;
	}
 
	// 設定内容を保存する
	Properties.Settings.Default.Save();
}




以上の手順で、プログラムが終了しても設定内容がapp.configに保持されます。





保存した値は、以下のような感じで取得可能です。

Properties.Settings.Default.Setting_Destination;




また、app.configファイルのありかですが、VisualStudioでデバッグ実行中は以下の場所にあります。

%LOCALAPPDATA%\Microsoft\プロジェクト名.vhost.exe_hashvalue




プログラムからapp.configの正確な場所を知りたい場合は、以下の処理で取得できます。

string configPath = System.Configuration.ConfigurationManager.OpenExeConfiguration( System.Configuration.ConfigurationUserLevel.PerUserRoamingAndLocal).FilePath;


但し、System.Configuration.ConfigurationManagerを使用するためには、プロジェクトの参照設定でSystem.Configurationを追加しておく必要があります。

[C#]タスクトレイアプリケーションを作る時に、最初にすること。

1.フォームにNotifyIconと,ContextMenuStripコントロールを貼り付ける

2.NotifyIconのContextMenuStripで、先ほど追加したコントロールを選択する

3.NotifyIconのIconを設定する(忘れがちなので注意!!)

4.ContextMenuStripのメニューに”終了”を追加し、以下のコードを追加する

//*********************************************************************
/// <summary> プログラム終了が選択されたときのハンドラ
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
//*********************************************************************
private void MenuItem_Close_Click( object sender, EventArgs e ) {
	TaskTrayIcon.Visible = false;
	Application.Exit();
}




5.Program.csを以下のように書き換える

Application.Run( new FrmMain() );


 ↓

// 起動時にフォームを表示させない
//Application.Run( new FrmMain() );
Form form = new FrmMain();
Application.Run();




以上で、タスクトレイアプリの雛形が作成できる。

[JavaScript]即時関数の呼び出しが,なぜ「(function(){…})()」の形式になるか説明してみる。

JavaScriptでは即時関数という仕組みがあります。

即時関数は、グローバルな名前空間を汚さない無名関数を作って、その関数を直ぐにコールする方法です。
コードは、以下のような形式になります。

(function() {
	alert( "hello" );
})();



jQueryとかのサンプルを見ると良く見る形式ですが、なぜこの形式で即時関数を定義できるのか、
改めて考えてみます。




まず、無名関数を作ります。

function() {
	alert( "hello" );
}




function()…の固まりは、関数を返すはず。
これは、変数に代入するパターンで考えればイメージしやすいです。

// function()...で生成した関数オブジェクトを変数に代入している
var showMessage = function() {
	alert( "hello" );
}




関数の呼び出しは関数オブジェクトの後に”()”をつければコールできることになります。
なので、最後に()をつける。

// 挨拶を表示する関数オブジェクトを作って直ぐに実行する
var showMessage = function() {
	alert( "hello" );
}();


でも、一回しか呼ばれない処理の場合、showMessageという変数に代入する必要はないです。
というか,無駄に変数を増やししたくない。


なので、変数に代入する替わりに,()演算子で無名関数のオブジェクトを取得し、直ぐにコールします。

(function() {
	alert( "hello" );
})();


故に、「(function(){…})()」の形式によって、無名関数を即時呼び出し出来る即時関数を作ることが出来ます。また、当然ながら、この関数に対して名前は付いてないので、同じ処理を再呼び出すことは出来ないです。




この即時関数、再呼び出しが出来ないので一見すると不便そうですが、何の利点があるのでしょうか?
これは、ページのonLoad処理や各種初期化処理はそもそも複数回呼び出す必要はないし、再利用しないものに対して名前をつけると他の処理(他の人が作ったライブラリとか)と名前が衝突する危険が出てきます。
なので、”名前を付けない関数”を作ってコールするという方法にメリットが出てきます。


自分で作った画面専用のスクリプトなら、このテクニックは必ずしも必要で貼りませんが、ブックマークレット、ブログパーツ、共通ライブラリなど、他の処理と干渉させてはいけないプログラムを作る場合には必須となる手法です。

[JavaScript]関数に、一度しか実行しない初期化処理を組み込む

関数に、コンストラクタのような一度しか実行されない初期化処理を組み込むことが出来ます。
下記の例では、getCounter()で関数オブジェクトを取得するたびに、初期化処理がコールされます。

 
// 関数の定義
var getCounter = function() {
    // コンストラクタ的な、初期化処理
    console.log( "initialize" );
 
    // この変数は呼び元から見えない(privateな状態になる)
    var counter = 0;
 
    return function() {
        console.log( "count :" + counter );
        return counter+=1;
    };
};
 
 
// 呼び出しのテスト
var counter = getCounter();     // ここで、"initialize"が表示される。
alert( counter() );
alert( counter() );
 
var counter = getCounter();     // ここで、"initialize"が表示される。
alert( counter() );
alert( counter() );



consoleの出力結果

initialize
count :0
count :1
initialize
count :0
count :1






さらに、クラスイニシャライザのような、プログラム中最初にコールされたときのみ実行される処理化処理を組み込むことも出来ます。

 
// 関数の定義
var getCounter = function() {
    console.log( "class initializer" );
 
 
    // ここで自分自身の関数を再定義する
    // (なので、これより上にある処理はプログラム中で一回しか呼ばれない)
    getCounter = function() {
        // コンストラクタ的な、初期化処理
        console.log( "initialize" );
        // この変数は呼び元から見えない(privateな状態になる)
        var counter = 0;
 
        return function() {
            console.log( "count :" + counter );
            return counter+=1;
        };
    };
};
 
 
// 呼び出しのテスト
var counter = getCounter();     // ここで、"initialize"が表示される。
alert( counter() );
alert( counter() );
 
var counter = getCounter();     // ここで、"initialize"が表示される。
alert( counter() );
alert( counter() );




consoleの出力結果

class initializer
initialize
count :0
count :1
initialize
count :0
count :1

[JavaScript]例外発生時に、catch句でエラーの発生場所を取得する

JavaScriptのtry-catchのcatch句で取得できる例外オブジェクトは、エラーの内容を伝えるためにnameとmessageプロパティを持っています。

JavaScriptの標準規格では、例外オブジェクトが持っているプロパティは2つのみですが、firefox等の一部のブラウザでは、これらに加えてエラーが発生したファイルや行番号の情報も取得することが可能です。

これらの情報はfirefoxの場合はfileName,lineNumberで取得できますが、標準仕様からの拡張である為、IEでは取得することが出来ません。


ブラウザの互換性まで考慮したうえで、以下のようなエラー処理を書いておくとデバッグ作業が捗ります。

function errorFunc() {
    try {
        // 例外を発生させる(存在しないオブジェクトのプロパティを参照)
        a.data;
 
    } catch ( e ) {
        var ermsg = "例外が発生しました :" + e.message;
 
        if ( e.fileName && e.lineNumber ) {
            // エラーの発生場所が取れる場合は、情報を追加する
            ermsg += "\nファイル:" + e.fileName + ", 行:" + e.lineNumber;
        }
        console.log( ermsg );
    }
}
 
errorFunc();


[JavaScript]文字列リテラルと、new String()の違い。

JavaScriptの書籍を読むと、文字列の定義において以下の2つが同じであるという説明をしている場合があります(入門書だと良く有る説明です)。

case1:

var msg = new String( "hello" );



case2:

var msg = "hello";



確かに両者ともalert( msg );で、メッセージを出力できるので一見すると同じに見えるのですが、実は一緒では有りません。
両者の違いは、下記のコードを実行してみると分かります。

var msg = new String( "hello" );
msg.lang ="en";
alert( msg.lang );




var msg = "hello";
msg.lang ="en";
alert( msg.lang );




前者は期待通り”en”と出力してくれますが、後者はundefinedと出力されてしまいます。

このことから分かるのは、new String()でオブジェクトとして文字列を生成した場合は、プロパティを保持することが可能ですが、プリミティブ型として作った純粋な文字列リテラル(後者の例)の場合はプロパティを持つことが出来ないということです。

また、後者の例において、「msg.lang =”en”;」の部分では、エラーは出ませんが代入値は無視されます)

通常プログラムを作る場合は、余り気にする必要は無いかもしれませんが、ライブラリを設計する場合に注意が必要となる場合が出て来るため、注意が必要なポイントです。


ちなみに、下記のようにnewを書かずにString()をコールした場合は、プリミティブ型の文字列が返されるため、結果はundefinedになります。

var msg = String( "hello" );
msg.lang ="en";
alert( msg.lang );


こちらも見落としやすいポイントなので気をつける必要がありますね。

HDDをSSDに交換した際に,電気代の節約効果

基礎資料:

1kWhの電気代 : 22円程度
 
SSDの消費電力: 
	アイドル時   100mW
	アクティブ時 150mW
	0.05W
	Intel320シリーズ[SSDSA2CW080G3K5]の場合(80GB)
 
HDDの消費電力: 
	アイドル時               8.4W
	ランダムリード/ライト時 12.3W
	HDS721010の場合(1TB,7200rpm 2011年製のHDD)
 
	アイドル時              12W
	ランダムリード/ライト時 23W 
	Maxtor 7L300R0の場合(300GB,7200rpm 2008年製のHDD)





1ヶ月使いっぱなしにした場合の電気代:

SSD
	0.15 * 24 * 30   =108(W/月)
	108 * 22 * 0.001 =2.4(円/月)
 
HDS721010(現データDisk)
	12.3 * 24 * 30    =8856(W/月)
	8856 * 22 * 0.001 =194.8(円/月)
 
Maxtor 7L300R0(現起動Disk)
	23 * 24 * 30       =16560(W/月)
	16560 * 22 * 0.001 =364.32(円/月)





何年でペイするか:

SSDSA2CW080G3K5の価格(2012/2現在)
	12,000円程度
 
Maxtor 7L300R0を交換すると...
	12000 / 364.32 = 33ヶ月 -> 2.7年




もっと小容量にすると…
40GBだと、7400円なので20.3ヶ月(1.7年)でペイする。

でも、信頼性を考えると…
SSDは書き換え回数の制限があるので、容量に余裕を持たせた方が寿命は長持ちする。
24時間起動の起動ディスクを置き換えたいので、80GBの方がベターかもしれない。


2012/07追記:実際に購入して入れ替えを行いました。その時の内容はこちらです。


SSDと競合メディアの最新動向と将来展望
節電・停電対策から安否確認まで 自宅につくる震災対処PCシステム

zenbackのスクリプトを解析してみた(3/3)


前回の続きです。

cssとhtmlには特に見るべき所が無さそうなので、javascriptだけチェックしていきます。

zb_jq("div.zenback-twitterbtn > a:first").click(function() {
  this.disabled = true;
  var title = (document.title || "");
  if (title) {
    title += " → ";
  }
  window.top.location = "http://widget.zenback.jp/_t/twitter_post_redirect/?url=http%3A//nanoappli.com/blog/&title="+encodeURIComponent(title);
}); //end click




div.zenback-twitterbtnがクリックされたらページ遷移するスクリプトですが、zenback-twitterbtnの定義自体がhtmlに無かったので既に使われてないコードっぽいです。
実際に上記URLにアクセスしてみても、以下のようなエラーでした。



ちなみに余談ですが、このエラーページの右下にあるbluebridge、以下の企業でした。
http://bluebridge.jp/


zb_escape_html()


var zb_escape_html = function (str) {
  str = str.replace("&","&amp;");
  str = str.replace("\"","&quot;");
  str = str.replace("'","&#039;");
  str = str.replace("<","&lt;");
  str = str.replace(">","&gt;");
  return str;
};


HTMLエンコードの処理です。
これとまったく同じ処理。→JavaScript/jQuery HTML Encoding
どこからも呼ばれていない模様なので過去の遺産かも。



zb_load_script()


var zb_load_script = function (options) {
  var script = document.createElement('script');
  script.src = options.url;
  script.type = 'text/javascript';
  script.charset = 'utf-8';
  if (options.async) {
    script.async = true;
  }
  if (window.ActiveXObject) { // for IE
    script.onreadystatechange = function () {
      if (this.readyState === 'onloaded' || this.readyState === 'complete') {
        options.onload && options.onload();
      }
    };
  } else { // for other
    script.onloaddone = false; // for opera
    script.onload = function () {
      if (!this.onloaddone) {
        options.onload && options.onload();
        this.onloaddone = true;
      }
    };
  }
  if (options.element) {
    var s = options.element;
    s.appendChild(script);
  } else { // default insert
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(script, s);
  }
};


引数で指定されたoptions.urlのスクリプトを実行する処理。
options.urlにはjavascriptのurlが指定される。
また、options.asyncがtrueの時は非同期で処理がコールされ、JavaScriptのロードが完了したタイミングでoptions.onloadをコールバックしてくれる。
ブラウザ毎の非互換性の吸収とかしてますね。

この辺の処理は汎用性が有りそう。



_zb_print_error()


var _zb_print_error = function (msg) {
  window.console && console.error(msg);
};


デバッグログ的な関数。
console(window.console)は、クライアントよっては存在しない可能性があるので、チェックしてますね。



zb_load_jsonp()


var zb_load_jsonp = function (params, callback) {
  if (!params || !callback) {
    throw new Error('Invalid Parameter');
  }
  try {
    var cb_key = params.callbackKey || 'callback';
    var cb_value = params.callbackValue || 'callback';
    var url = (params.url + (params.url.match(/\?/) ? '&' : '?'));
    url += (cb_key + '=' + cb_value);
    var frame = document.createElement("iframe");
    frame.style.display = "none";
    document.body.appendChild(frame);
    var doc = frame.contentWindow.document;
    var count = 0; // for Opera
    frame[frame.readyState/*IE*/ ? "onreadystatechange" : "onload"] = function () {
      if (this.readyState && this.readyState !== 'complete' || count++) {
        return;
      }
      if (doc['__zb_jsonp__']) {
        callback(null, doc['__zb_jsonp__']);
      } else if (params.retry && params.retry >= 1) {
        var timeout = params.timeout || 1000;
        setTimeout(function () {
          zb_load_jsonp({
            url: params.url
            ,callbackKey: cb_key
            ,callbackValue: cb_value
            ,retry: params.retry - 1
            ,timeout: timeout 
          }, callback)}, timeout);
      } else {
        callback({
          message: 'Failed load jsonp'
        });
      }
      setTimeout(function () {
        try {
          frame && frame.parentNode && frame.parentNode.removeChild(frame);
        } catch (e) {
          _zb_print_error(e.message);
        }
      }, 0);
    };
    doc.open();
    doc.write('<' + 'script type="text/javascript">'
        + 'function ' + cb_value + ' (v) { document["__zb_jsonp__"] = v };'
        + '</' + 'script>'
        + '<' + 'script type="text/javascript" src="' + url + '"></' + 'script>');
    doc.close();
    return frame;
  } catch (e) {
    callback(e);
  }
};


なんだろう…
雰囲気的にはiframeを作って、jsonpの呼び出しの感じだけど、ちょっと複雑なので後回し。
zb_load_twitter_favorites()からコールされている。


zb_abort_jsonp()


var zb_abort_jsonp = function (frame) {
  try {
    frame && frame.parentNode && frame.parentNode.removeChild(frame);
  } catch (e) {
    _zb_print_error(e.message);
  }
};


jsonpコールを中断したとき、一旦作ったiframeを消しに走っている。
残念ながら誰からも呼ばれてない。



script_container()


var script_container = document.getElementById('zenback-script-container');
 
var zb_ga_track_settings = function () {
  _gaq.push(['zb._setAccount', 'UA-17145123-2']);
  _gaq.push(['zb._trackPageview']);
  _gaq.push(['zb._trackEvent', 'widgetNSID', '353091eea229c2955b2271a0c215dd8bcb210a67']);
  _gaq.push(['zb_ads._setAccount', 'UA-17145123-5']);
};


google analysticsの下準備。


zb_ga_track_XXXX()


var zb_ga_track_entries = function () {
  zb_jq('.zenback-entries .zenback-list a').click(function () {
    _gaq.push(['zb._trackEvent', 'kanren_entries', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_links = function () {
  zb_jq('.zenback-links .zenback-list a').click(function () {
    _gaq.push(['zb._trackEvent', 'kanren_links', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_keywords = function () {
  zb_jq('.zenback-keywords .zenback-list a').click(function () {
    _gaq.push(['zb._trackEvent', 'kanren_keywords', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_newsitem = function () {
  zb_jq('.zenback-newsitem a').click(function () {
    _gaq.push(['zb._trackEvent', 'zenback_news', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_twitter_widget = function () {
  zb_jq('.zenback-twitter .zenback-list a').click(function() {
    _gaq.push(['zb._trackEvent', 'twitter', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_mixi = function () {
  zb_jq('.zenback-socialbar .zenback-socialbar-mixicheck a').click(function () {
    _gaq.push(['zb._trackEvent', 'mixi', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};
 
var zb_ga_track_evernote = function () {
  zb_jq('.zenback-socialbar .zenback-socialbar-evernote a').click(function () {
    _gaq.push(['zb._trackEvent', 'evernote', encodeURI(this.href), 'http://nanoappli.com/blog/']);
  });
};


アクセスログを取る際に、どのボタンが押されたかクリックログを仕込んでいる。
ボタンクリックのイベントハンドラを登録し、クリックのタイミングでどのボタンが押されたかgoogle analysticsのトラッキング用変数に値をセットしてる。
_gaq.pushの第二引数が違うだけなんだから共通関数にしてください!! な所。


zb_ga_track_XXXX()再び


var zb_ga_track_hatena_bookmark = function () {
  _gaq.push(['zb._trackEvent', 'hatena_bookmark', encodeURI(this.href), 'http://nanoappli.com/blog/']);
};
 
var zb_ga_track_googleplusone = function (obj) {
  _gaq.push(['zb._trackSocial', 'google+1', obj.state, obj.href]);
};
 
var zb_ga_track_twitter_tweet = function (url) {
  _gaq.push(['zb._trackSocial', 'twitter', 'tweet', url]);
};
 
var zb_ga_track_twitter_follow = function () {
  _gaq.push(['zb._trackSocial', 'twitter', 'follow']);
};
 
var zb_ga_track_fb_comments = function (comment) {
  _gaq.push(['zb._trackSocial', 'facebook', 'comment', comment.href]);
};
 
var zb_ga_track_fb_uncomments = function (comment) {
  _gaq.push(['zb._trackSocial', 'facebook', 'uncomment', comment.href]);
};


さっきと同じ。
今回はまったく同じでは無いけど、もう少し共通関数できると思うんだけど。



google analsticsのロード処理


zb_load_script({
  url: ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
  ,async: true
  ,onload: function () {
    zb_ga_track_settings();
    zb_ga_track_entries();
    zb_ga_track_links();
    zb_ga_track_newsitem();
    zb_ga_track_keywords();
    zb_ga_track_twitter_widget();
    zb_ga_track_mixi();
    zb_ga_track_evernote();
  }
});


google analsticsのロード処理を,zb_load_script()経由でコールしている。
zb_load_script()の中身は既に前述済み。
gaのロードが完了したタイミングで、前述のクリックログの仕込みに入っている。



tweetボタン関係のロード処理


zb_load_script({
  url: 'http://platform.twitter.com/widgets.js'
  ,element: script_container
  ,async: true
  ,onload: function () {
    var zb_extract_param_from_uri = function (uri, paramName) {
      if (!uri) {
        return '';
      }
      var uri = uri.split('#')[0];  // Remove anchor.
      var parts = uri.split('?');  // Check for query params.
      if (parts.length == 1) {
        return '';
      }
      var query = decodeURI(parts[1]);
 
      // Find url param.
      paramName += '=';
      var params = query.split('&');
      for (var i = 0, param; param = params[i]; ++i) {
        if (param.indexOf(paramName) === 0) {
          return unescape(param.split('=')[1]);
        }
      }
    };
    if (window.twttr) {
      window.twttr.events.bind('tweet', function (event) {
        if (event) {
          var targetUrl;
          if (event.target && event.target.nodeName == 'IFRAME') {
            targetUrl = zb_extract_param_from_uri(event.target.src, 'url');
          }
          zb_ga_track_twitter_tweet(targetUrl);
        }
      });
      window.twttr.events.bind('follow', function (event) {
        event && zb_ga_track_twitter_follow();
      });
    }
  }
});


twitter公式のtweetボタン関係のライブラリをロードしてる。
ライブラリAPIは下記のページに説明があります。
https://dev.twitter.com/docs/tweet-button



hatenaボタン関係のロード処理


zb_load_script({
  url: 'http://b.st-hatena.com/js/bookmark_button.js'
  ,element: script_container
  ,async: true
  ,onload: function () {
  }
});


はてなのボタン。



google+1ボタン関係のロード処理


window.___gcfg = {
  lang: 'ja'
  ,parsetags: 'explicit'
};
zb_load_script({
  url: 'https://apis.google.com/js/plusone.js'
  ,element: script_container
  ,async: true
  ,onload: function () {
    gapi.plusone.render('zenback-google-plusone', {
      'size': 'medium'
      ,'count': 'true'
      ,'callback': 'zb_ga_track_googleplusone'
    });
  }
});


googleのボタン
APIはこちら→ +1 Button


zb_render_ad_view()


var zb_render_ad_view = function (params) {
  var result = false;
  try {
    var $zb_ads_body = zb_jq('#zenback-ads span.tweet_body');
    var $zb_ads_text_a = zb_jq('<a></a>')
      .attr('href', params.link_url)
      .attr('target', '_blank')
      .addClass('zenback-ads-ad')
      .click(params.ga_click_tracking_code);
    var $zb_ads_link_span = zb_jq('<span></span>')
      .addClass('link');
    $zb_ads_link_span.append($zb_ads_text_a.clone().text(params.view_url));
    $zb_ads_text_a.text(params.ad_text);
    var $zb_ads_metadata_span = zb_jq('<span></span>')
      .addClass('metadata');
    var $zb_ads_author_span = zb_jq('<span></span>')
      .addClass('author');
    var $zb_ads_twitter_a = zb_jq('<a></a>')
      .attr('href', params.twitter_url)
      .attr('target', '_blank')
      .addClass('zenback-ads-twitter')
      .click(params.ga_click_tracking_code);
    var $zb_ads_twitter_a_clone = $zb_ads_twitter_a.clone()
      .text('@' + params.twtiter_screen_name);
    var $zb_ads_img = zb_jq('<img />')
      .attr('src', params.twitter_icon_url)
      .attr('alt', params.twtiter_screen_name);
    $zb_ads_twitter_a.append($zb_ads_img);
    var $zb_ads_strong = zb_jq('<strong></strong>')
      .append($zb_ads_twitter_a_clone);
    $zb_ads_author_span.append($zb_ads_twitter_a)
      .append($zb_ads_strong)
      .append(zb_jq('<br />'))
      .append(zb_jq('<span></span>').text(params.twtiter_screen_name));
    $zb_ads_metadata_span.append($zb_ads_author_span);
    $zb_ads_body.append($zb_ads_text_a)
      .append(zb_jq('<br />'))
      .append($zb_ads_link_span)
      .append($zb_ads_metadata_span)
      .show();
    result = true;
  } catch (e) {
    _zb_print_error(e.message);
  }
  return result;
};


zenbackの広告のエリア。
なぜ全部動的に生成する必要があるんだろう??
jQuery使ってるなら変数部だけ差し込めばいい気もするんだけど。



zb_show_default_ad_view()


var zb_show_default_ad_view = function (ga_category) {
  var ga_category = ga_category || 'default';
  var link_url = 'http://zenback.jp/?s=ads_fl01';
  var view_url = 'http://zenback.jp';
  var result = zb_render_ad_view({
    link_url: link_url
    ,view_url: view_url
    ,ad_text: 'zenbackは自分のブログの記事と、過去の自分の記事、他のブログ記事、TwitterやFacebookなどソーシャルメディアをつなげるウィジェットです。多くのブログサービスに対応。設置はコードをコピペするだけ、5分で完了します。ご利用は無料です。'
    ,twitter_url: 'http://twitter.com/zenback'
    ,twitter_icon_url: 'http://a1.twimg.com/profile_images/1185414381/zenback_icon_normal.png'
    ,twtiter_screen_name: 'zenback'
    ,ga_click_tracking_code: function () {
      _gaq.push(['zb_ads._trackEvent', ga_category, 'click', link_url]);
    }
  });
  if (result) {
    _gaq.push(['zb_ads._trackPageview', ga_category, link_url]);
  } else {
    _zb_print_error('Failed the default ad rendering.');
  }
};


諸々のエラーでロードが出来なかったときに、デフォルトで表示される広告の情報を作ってる。



zb_load_twitter_favorites(), zb_select_ad_tweet(), zb_show_ad_view()


var zb_load_twitter_favorites = function (twitter_id, load_count, callback) {
  try {
    var retry = 4;
    var favorites_api_base_url = 'https://api.twitter.com/1/favorites.json';
    var favorites_api_call_url = favorites_api_base_url + '?id=' + twitter_id + '&count=' + load_count;
    zb_load_jsonp({ url: favorites_api_call_url, retry: retry }, function (err, jsonp) {
      if (err) {
        callback(err);
        return;
      }
      callback(null, jsonp);
    });
  } catch (e) {
    callback(e);
  }
};
var zb_select_ad_tweet = function (free_tweets, filler_tweets, callback) {
  try {
    if (!free_tweets || !filler_tweets) {
      callback({
        message: 'Invalid parameter'
      }, null);
      return;
    }
    var timestamp = (+new Date());
    var select_index = timestamp % 40;
    if (select_index < free_tweets.length) {
      callback(null, {
        ad: free_tweets[select_index]
        ,ga_category: 'zbcf'
      });
    } else {
      if (filler_tweets.length === 0) {
        callback({
          message: 'Nothing filler'
        }, null);
      } else {
        callback(null, {
          ad: filler_tweets[timestamp % filler_tweets.length]
          ,ga_category: 'zbcf2'
        });
      }
    }
  } catch (e) {
    _zb_print_error(e.message);
    callback(e);
  }
};
var zb_show_ad_view = function (tweet, ga_category) {
  if (!tweet) {
    return;
  }
  var link_url = 'https://twitter.com/#!/' + tweet.user.screen_name + '/status/' + tweet.id_str;
  var view_url = link_url;
  var url_regex = /((?:https?):\/\/[!-~]+)/m;
  if (tweet.text.match(url_regex)) {
    link_url = RegExp.$1;
    view_url = RegExp.$1;
  }
  var twitter_url = 'http://twitter.com/' + tweet.user.screen_name;
  var result = zb_render_ad_view({
    link_url: link_url
    ,view_url: view_url
    ,ad_text: tweet.text
    ,twitter_url: twitter_url
    ,twitter_icon_url: tweet.user.profile_image_url
    ,twtiter_screen_name: tweet.user.screen_name
    ,ga_click_tracking_code: function () {
      _gaq.push(['zb_ads._trackEvent', ga_category, 'click', tweet.id_str]);
    }
  });
  if (result) {
    _gaq.push(['zb_ads._trackPageview', ga_category, tweet.id_str]);
  } else {
    _zb_print_error('Failed the free ad rendering.');
  }
};


読むのがめんどくさくなってきたので省略…
twitterのお気に入りとか広告の表示処理っぽい。



広告のロード処理


try {
  var ads_free_id = 'zbcf';
  var ads_free_load_number = 40;
  zb_load_twitter_favorites(ads_free_id, ads_free_load_number, function (err, data) {
    if (err) {
      _zb_print_error(err.message);
      zb_show_default_ad_view();
      return;
    }
    var ads_free_tweets = data; 
    var ads_filler_id = 'zbcf2';
    var ads_filler_load_number = 40;
    zb_load_twitter_favorites(ads_filler_id, ads_filler_load_number, function (err, data) {
      if (err) {
        _zb_print_error(err.message);
        zb_show_default_ad_view();
        return;
      }
      var ads_filler_tweets = data;
      zb_select_ad_tweet(ads_free_tweets, ads_filler_tweets, function (err, data) {
        if (err) {
          _zb_print_error(err.message);
          zb_show_default_ad_view();
          return;
        }
        zb_show_ad_view(data.ad, data.ga_category);
      });
    });
  });
 
} catch (e) {
  _zb_print_error(e.message);
  zb_show_default_ad_view();
}
  })();


さっき省略した広告等のロード処理。
twitterのIDが@zbcfか@zbcf2のツイート内容を出力している?
ロードに失敗したときでも、zb_show_default_ad_view()でデフォルトの広告を出そうとしている所は良い感じ(作り手側の視点として)。




…と、ざっくり全処理を見てみました。
個人的に、気になった事や、後でもう少し勉強しておきたい箇所を纏めておきます。

  • zb_load_script()の非同期処理(コールバックあり)は汎用性がありそう
  • 各ボタンの、クリックログをとる仕組み
  • google analysticsの、_gaq.pushに関する仕様を押さえておく
  • _zb_print_error()は、console.logではなくってajaxでサーバサイドに投げておくと便利かも。
  • zb_ga_track_xxx()関数郡のつくりは明らかにおかしいよね??(共通関数化して欲しい)

あと、ソーシャルブックマーク系ボタンの処理を調べてるときに見つけたサイトで、以下のページが勉強になりそうでした。
はてなダイアリーにいいねボタンを置く方法
【図解】iGoogleガジェットをブログパーツとして活用する方法。


zenbackのスクリプトを解析してみた(2/3)


前回、以下のURLでzenbackのブログパーツ本体を取得しているところまで解析しました。

http://widget.zenback.jp/?callback=jsonp1328934279143&base_uri=http%3A%2F%2Fnanoappli.com%2Fblog&
rand=276590972233&nsid=101368938457261269%3A%3A101368959663646496&stage=1&motouri=...




今回は、上記URLのレスポンスを確認して行きます。

早速ですが、レスポンスの内容をfirebugで見た結果です。
9kbyte以上と結構なボリュームのレスポンスが返ってきているのが分かります。



非常に長いですが、JSONPなのでjavascriptの関数を1つコールしてるだけです。

jsonp1328934279143("....");




このままでは理解できないので、例によって関数の実行結果を取得します。
全部で1400行以上にわたる長編なので、適当に区切りを入れます。
大枠は「1:小さなscript」 + 「2:CSS」 + 「3:HTML」 + 「4:script本編」の4部構成になっています。

1:小さなscript


<script type="text/javascript">
    zb_jq("div.zenback-twitterbtn > a:first").click(function() {
      this.disabled = true;
      var title = (document.title || "");
      if (title) {
        title += " → ";
      }
      window.top.location = "http://widget.zenback.jp/_t/twitter_post_redirect/?url=http%3A//nanoappli.com/blog/&title="+encodeURIComponent(title);
    }); //end click
 
</script>



2:css


<style type="text/css">
/*
zenback default style
----------------------------------------------------------------------*/
.zenback {
	font-size:13px;
	padding:12px 0;
	line-height:18px;
}
.zenback .zenback-module{
	clear:both;
	padding-bottom:12px;
}
.zenback .zenback-heading{
	font-size:14px;
	margin:0 0 0 10px;
	padding-left:23px;
	float:left;
	display:inline;
}
.zenback .zenback-entries .zenback-heading {
	background:url(http://widget.zenback.jp/_p/images/icon-article.png?v=86b06) 0 center no-repeat #7fc527;
}
.zenback .zenback-links .zenback-heading {
	background:url(http://widget.zenback.jp/_p/images/icon-link.png?v=c4ac4) 0 center no-repeat #b0c906;
}
.zenback .zenback-mixi .zenback-heading {
	background:url(http://widget.zenback.jp/_p/images/mixi_icon.png?v=26695) 4px center no-repeat #f5b868;
}
.zenback .zenback-evernote .zenback-heading {
	background:url(http://widget.zenback.jp/_p/images/evernote_icon.png?v=a2195) 4px center no-repeat #7fc527;
}
.zenback .zenback-keywords .zenback-heading {
background:url(http://widget.zenback.jp/_p/images/icon-key.png?v=be6c2) 0 center no-repeat #C58E68;
}
.zenback .zenback-facebook .zenback-heading {
    background:url(http://widget.zenback.jp/_p/images/facebook_icon.png?v=f82ea) 4px center no-repeat #3B5998;
}
 
.zenback .zenback-entries ul {
	border-top:1px solid !important;
	border-top-color:#7fc527 !important;
	padding-top:7px !important;
}
.zenback .zenback-links ul {
	border-top:1px solid !important;
	border-top-color:#b0c906 !important;
	padding-top:7px !important;
}
.zenback .zenback-keywords ul {
	border-top:1px solid !important;
	border-top-color:#C58E68 !important;
	padding-top:7px !important;
}
.zenback .zenback-keywords ul:after {
	content:".";
	display:block;
	visibility:hidden;
	height:0.1px;
	font-size:0.1em;
	line-height:0;
	clear:both;
}
.zenback .zenback-facebook ul {
	border-top:1px solid !important;
	border-top-color:#3B5998 !important;
	padding-top:7px !important;
}
.zenback .zenback-facebook ul:after {
	content:".";
	display:block;
	visibility:hidden;
	height:0.1px;
	font-size:0.1em;
	line-height:0;
	clear:both;
}
 
.zenback img {
	border:none !important;
}
.zenback .zenback-pending{
  background:#f6f6f6 !important;
  text-align:center !important;
  margin:0 0 10px 0 !important;
  color:#555 !important;
  padding:5px 0 !important;
  clear:both !important;
  border-top:1px solid !important;
	border-top-color:#7fc527 !important;
}
.zenback .zenback-keywords-pending {
  background:#f6f6f6 !important;
  text-align:center !important;
  margin:0 0 10px 0 !important;
  color:#555 !important;
  padding:5px 0 !important;
  clear:both !important;
  border-top:1px solid !important;
	border-top-color:#C58E68 !important;
}
.zenback .zenback-heading span {/* tab */
	color:#ffffff !important;
	padding:2px 5px 0 !important;
	line-height:170% !important;
	font-weight:bold !important;
	vertical-align:middle !important;
	display:block !important;
	float:left !important;
	border-left:1px solid #fff !important;
}
.zenback .zenback-entries .zenback-heading span{
	background:#7fc527 !important;
}
.zenback .zenback-links .zenback-heading span{
	background:#b0c906 !important;
}
.zenback .zenback-mixi .zenback-heading span{
	background:#f5b868 !important;
}
.zenback .zenback-evernote .zenback-heading span{
	background:#7fc527 !important;
}
.zenback .zenback-keywords .zenback-heading span{
	background:#C58E68 !important;
}
.zenback .zenback-facebook .zenback-heading span {
	background:#3B5998 !important;
}
.zenback .zenback-module ul {
	padding:5px 10px 0 10px !important;
	margin:0 0 10px 0 !important;
	clear:both !important;
	word-wrap:break-word !important;
	zoom:1 !important;
}
.zenback li {
	list-style:none !important;
	/*display:block !important;*/
	text-align:left !important;
	float:none !important;
}
.zenback ul.zenback-list {
	zoom:1;
	margin:0 !important !important;
}
.zenback ul.zenback-list li {/* icon list */
  /*
	padding-left:24px !important;
	margin-bottom:5px !important;
	background:transparent url(http://widget.zenback.jp/_p/images/icon-listmark.gif?v=31a7b) no-repeat 0 3px !important;
	*/
}
.zenback .zenback-entries ul.zenback-list li {
  padding-left:24px !important;
	margin-bottom:5px !important;
	background:transparent url(http://widget.zenback.jp/_p/images/icon-listmark.gif?v=31a7b) no-repeat 0 3px !important;
}
.zenback .zenback-links ul.zenback-list li {
  padding-left:24px !important;
	margin-bottom:5px !important;
	background:transparent url(http://widget.zenback.jp/_p/images/icon-listmark-link.gif?v=08139) no-repeat 0 3px !important;
}
.zenback .zenback-keywords ul.zenback-list li {
	float:left !important;
	padding-left:15px !important;
	padding-right:5px !important;
	/*background:transparent url(http://widget.zenback.jp/_p/images/key.png?v=3b51a) no-repeat 0 3px !important;*/
  background: #ebebeb !important;
  margin: 0 8px 8px 0 !important;
  padding: 2px 10px 2px !important;
  border: 1px solid #fff !important;
  -moz-box-shadow: 3px 3px #b2b2b2;
  -webkit-box-shadow:3px 3px #b2b2b2;
	box-shadow:3px 3px #b2b2b2;
	filter: progid:DXImageTransform.Microsoft.Shadow(strength=3, direction=135, color='#b2b2b2');
	-ms-filter: "progid:DXImageTransform.Microsoft.Shadow(strength=3, Direction=135, Color='#b2b2b2')";
	border-radius:8px;
	-webkit-border-radius:8px;
	-moz-border-radius:8px;
}
.zenback .zenback-keywords ul.zenback-list li:hover {
  background: #cbcbcb !important;
}
.zenback .zenback-keywords ul.zenback-list li a{
  color:#1e3bf7 !important;
  text-decoration:none !important;
  background:none !important;
}
.zenback .zenback-keywords ul.zenback-list li a:hover {
  color:#1e3bf7 !important;
  text-decoration:none !important;
  background: #cbcbcb !important;
}
 
.zenback li img {
	margin-right:3px !important;
	vertical-align:middle !important;
}
/* more read */
.zenback .zenback-morereadbtn{
	text-align:center !important;
	border:1px solid #c5c5c5 !important;
}
.zenback .zenback-morereadbtn a{
	display:block !important;
	padding:3px 0 !important;
}
.zenback .zenback-morereadbtn a:hover{
}
 
.zenback-itemdate {
  font-size:11px;
  color:#999;
}
 
/* powerd by */
.zenback .zenback-powered {
		border-top-style:solid;
	border-top-width:1px;
	border-top-color:#7fc527;
		padding:0;
	text-align:right;
	zoom:1;
  display:block; 
  height:auto; 
  visibility:visible; 
}
.zenback .zenback-powered:after {
	content:".";
	display:block;
	visibility:hidden;
	height:0.1px;
	font-size:0.1em;
	line-height:0;
	clear:both;
}
.zenback .zenback-powered span {
	background-color:#7fc527;	color:#ffffff;
	padding:0.3em 0.4em;
	font-weight:bold;
	display:block;
	float:right;
}
.zenback .zenback-powered span img {
	vertical-align: middle;
	padding: 0px !important;
	margin: 0px !important;
}
 
 
/* start of the 'powered by topsy' */
.zenback .zenback-powered-topsy {
	padding: 0;
	text-align: right;
	zoom: 1;
  display: block !important; 
  height: auto !important; 
  visibility: visible !important;
}
 
.zenback .zenback-powered-topsy:after {
	content: ".";
	display: block;
	visibility: hidden;
	height: 0.1px;
	font-size: 0.1em;
	line-height: 0;
	clear: both;
}
 
.zenback .zenback-powered-topsy span {
	color: #ffffff !important;
	padding: 0.3em 0.4em !important;
	font-weight: bold !important;
	display: block !important;
	float: right !important;
}
 
.zenback .zenback-powered-topsy span img {
	vertical-align: middle;
	margin: 0px !important;
	padding: 0px !important;
}
/* end of the 'powered by topsy' */
 
 
.zenback-hatebu,.zenback-twitter{
}
/* Twitter hatebu common */
.zenback .zenback-hatebu .zenback-social .zenback-heading,.zenback .zenback-twitter .zenback-heading{
}
.zenback .zenback-heading a{
    background:none !important;
    text-decoration:none !important;
}
.zenback .zenback-hatebubtn, .zenback .zenback-twitterbtn, .zenback .zenback-mixibtn, .zenback .zenback-evernotebtn, .zenback .zenback-facebookbtn {
	clear:both;
	padding:5px 0 !important;
	margin:0 0 5px 0 !important;
	background:transparent url(http://widget.zenback.jp/_p/images/line-dashed.gif?v=435f8) 0 bottom repeat-x;
	text-align:center;
}
 
.zenback .zenback-hatebubtn a,
.zenback .zenback-heading a,
.zenback .zenback-twitterbtn a,
.zenback .zenback-mixibtn a,
.zenback .zenback-facebookbtn a {
	text-decoration:none;
}
 
.zenback .zenback-hatebubtn img,.zenback .zenback-twitterbtn img{
  vertical-align:bottom !important;
}
.zenback .zenback-headingwrap{
	display:inline;
}
/* Twitter */
.zenback .zenback-twitter .zenback-heading {
	background:url(http://widget.zenback.jp/_p/images/icon-twitter.gif?v=11fc1) 3px center no-repeat #44c2e4;
}
.zenback .zenback-twitter .zenback-heading span {
	background-color:#44c2e4;
}
.zenback .zenback-twitter .zenback-heading span em{
	font-weight:normal !important;
	font-style:normal !important;
	font-size:86% !important;
	text-align:right !important;
	margin:0 !important;
	padding:0 !important;
	text-decoration:none !important;
}
.zenback .zenback-twitterbtn{
	border-top:1px solid #44c2e4;
}
.zenback .zenback-twitter-name {
	margin-right:5px;
}
.zenback .zenback-twitter-status {
    clear: both;
} 
.zenback-twitter ul.zenback-list li {
	margin-bottom:5px !important;
	background:none !important;
	padding-left:0 !important;
}
/* Twitter design */
.zenback .zenback-twitter ul{
	/*border-top:1px solid #44c2e4 !important;*/
	padding-top:5px !important;
	/*color:#000 !important;*/
}
.zenback-twitter ul.zenback-nodesign li {
	min-height:48px !important;
	margin-bottom:10px !important;
	padding-bottom:7px !important;
	border-bottom:1px solid #eeeeee !important;
	padding:0 0 5px 0 !important;
	text-indent:0 !important;
}
* html .zenback-twitter ul.zenback-nodesign li {
	height:48px !important;
}
.zenback-twitter ul.zenback-nodesign li img.zenback-twitter-icon {
	float: left !important;
	margin: 0px 5px 0px 0px !important;
}
.zenback-twitter ul.zenback-nodesign li a {
	text-decoration:underline !important;
	font-weight:normal !important;
	background:none !important;
	font-style:normal !important;
	color:#0000FF !important;
}
.zenback-twitter ul.zenback-nodesign li a:hover {
	text-decoration:none !important;
	background:none !important;
	font-weight:normal !important;
	background:none !important;
	font-style:normal !important;
}
.zenback-twitter ul.zenback-nodesign li .zenback-itemdate a{
    font-size:11px !important;
    color:#999 !important;
    text-decoration:none !important;
    font-weight:normal !important;
    font-style:normal !important;
    background:none !important;
    padding:0 !important;
}
.zenback-twitter ul.zenback-nodesign li .zenback-itemdate a:hover {
    text-decoration:underline !important;
    color:#999 !important;
    font-weight:normal !important;
    font-style:normal !important;
    background:none !important;
}
.zenback-twitter ul.zenback-nodesign li .web-intents img {
  float: none !important;
  margin: 0px !important;
  padding: 0px !important;
  display: inline !important;
}
 
/* hatebu */
.zenback .zenback-hatebu .zenback-heading {
	background:url(http://widget.zenback.jp/_p/images/icon-hatebu.gif?v=0e165) 3px center no-repeat #5279e7;
}
.zenback .zenback-hatebu .zenback-heading span {
	background-color:#5279e7 !important;
}
.zenback .zenback-hatebu .zenback-heading span em{
	font-weight:normal !important;
	font-style:normal !important;
	font-size:86% !important;
	text-align:right !important;
	margin:0 !important;
	padding:0 !important;
	text-decoration:none !important;
}
.zenback .zenback-hatebubtn{
	border-top:1px solid #5279e7 !important;
}
.zenback .zenback-hatebu ul {
	/*border-top:1px solid #5279e7 !important;*/
	padding-top:5px !important;
	color:#000 !important;
}
.zenback .zenback-hatebu ul li{
	margin-bottom:5px !important;
}
/* hatebu design */
.zenback-hatebu ul.zenback-nodesign {
	background:#edf1fd !important;
	margin:0 0 5px 0 !important;
	padding:5px 10px 5px 10px !important;
}
.zenback-hatebu ul.zenback-nodesign li {
	font-size:85% !important;
	margin-bottom:2px !important;
	background:none !important;
	padding-left:0 !important;
}
.zenback-hatebu ul.zenback-nodesign li img {
	vertical-align: middle !important;
	margin: 0px !important;
	padding: 0px !important;
}
.zenback-hatebu ul.zenback-nodesign .zenback-hatebu-date {
	margin-right:3px;
}
.zenback-hatebu ul.zenback-nodesign .zenback-hatebu-name {
	margin-right:3px;
}
.zenback-hatebu ul.zenback-nodesign .zenback-hatebu-tag {
	font-size:80%;
	margin-right:3px;
}
.zenback-hatebu ul.zenback-nodesign a {
	text-decoration:underline !important;
	font-weight:normal !important;
	background:none !important;
	font-style:normal !important;
	color:#0000FF !important;
}
.zenback-hatebu ul.zenback-nodesign a:hover {
	text-decoration:none !important;
	background:none !important;
	font-weight:normal !important;
	background:none !important;
	font-style:normal !important;
}
 
 
/* socialbar */
.zenback .zenback-socialbar .zenback-heading {
	background: url(images/icon-social.gif) 3px center no-repeat #5279e7 !important;
}
.zenback .zenback-socialbar .zenback-heading span {
	background-color: #5279e7 !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div {
  float: left !important;
  height: 30px !important;
}
.zenback .zenback-socialbar .zenback-socialbtn:after {
	content: "." !important;
	display: block !important;
	visibility: hidden !important;
	height: 0.1px !important;
	font-size: 0.1em !important;
	line-height: 0 !important;
	clear: both !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-twitter {
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-twitter iframe {
  float: left !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-twitter-count {
  list-style-type: none !important;
  background: none !important;
  position: relative !important;
  float: left !important;
  padding: 0px 5px 0px 0px !important;
  margin: 0px !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div div.zenback-socialbar-twitter-count-element {
  display: inline-block !important;
  position: relative !important;
  margin: 0 0 0 5px !important;
  -moz-box-sizing: border-box !important;
  -webkit-box-sizing: border-box !important;
  -ms-box-sizing: border-box !important;
  height: 20px !important;
  max-width: 100% !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div div.zenback-socialbar-twitter-count-element i {
  position: absolute !important;
  visibility: visible !important;
  line-height: 0 !important;
  width: 0 !important;
  height: 0 !important;
  left: 0 !important;
  top: 50% !important;
  margin: -4px 0 0 -4px !important;
  border: 4px transparent solid !important;
  border-right-color: #AAA !important;
  border-left: 0 !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div div.zenback-socialbar-twitter-count-element u {
  position: absolute !important;
  visibility: visible !important;
  line-height: 0 !important;
  width: 0 !important;
  height: 0 !important;
  left: 0 !important;
  top: 50% !important;
  border: 4px transparent solid !important;
  margin: -4px 0 0 -7px !important;
  border-right-color: white !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div div.zenback-socialbar-twitter-count-element span {
  visibility: visible !important;
  font: normal normal normal 11px/18px 'Helvetica Neue',Arial,sans-serif !important;
  text-align: center !important;
  color: #333 !important;
  padding: 2px 5px 4px 5px !important;
  background: white !important;
  border: #BBB solid 1px !important;
  -moz-border-radius: 3px !important;
  -webkit-border-radius: 3px !important;
  border-radius: 3px !important;
  min-height: 18px !important;
  _height: 18px !important;
  min-width: 15px !important;
  _width: 15px !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-twitter-follow {
  clear: both !important;
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-hatebu {
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-mixicheck {
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-evernote {
}
.zenback .zenback-socialbar .zenback-socialbtn div.zenback-socialbar-facebook {
}
 
 
/* socialbar design */
.zenback-socialbar div.zenback-nodesign {
  margin: 0px !important;
  padding-top: 0px !important;
  padding-right: 0px !important;
  padding-bottom: 0px !important; 
  padding-left: 10px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-twitter {
  margin: 0px !important;
  padding-top: 0px !important;
  padding-right: 10px !important;
  padding-bottom: 0px !important;
  padding-left: 0px !important;
  vertical-align: middle !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-twitter iframe {
  margin: 0px !important;
  padding: 0px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-twitter span {
  margin: 0px !important;
  padding: 0px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-hatebu {
  margin: 0px !important;
  padding-top: 0px !important; 
  padding-right: 10px !important;
  padding-bottom: 0px !important;
  padding-left: 0px !important;
  vertical-align: middle !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-hatebu iframe {
  margin: 0px !important;
  padding: 0px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-mixicheck {
  margin: 0px !important;
  padding-top: 0px !important;
  padding-right: 10px !important;
  padding-bottom: 0px !important;
  padding-left: 0px !important;
  vertical-align: middle !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-mixicheck a {
  margin: 0px !important;
  padding: 0px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-mixicheck a:hover {
	background: none !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-mixicheck img {
  vertical-align: middle !important;
  margin: 0px !important;
  padding: 0px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-evernote {
  margin: 0px !important;
  padding-top: 0px !important;
  padding-right: 10px !important;
  padding-bottom: 0px !important;
  padding-left: 0px !important;
  vertical-align: middle !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-evernote a {
  margin: 0px !important;
  padding: 0px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-evernote a:hover {
	background: none !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-evernote img {
  vertical-align: middle !important;
  margin: 0px !important;
  padding: 0px !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-google-plusone {
  margin: 0px !important;
  padding-top: 0px !important;
  padding-right: 10px !important;
  padding-bottom: 0px !important;
  padding-left: 0px !important;
  vertical-align: middle !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-facebook {
  margin: 0px !important;
  padding-top: 0px !important;
  padding-right: 10px !important;
  padding-bottom: 0px !important;
  padding-left: 0px !important;
  vertical-align: middle !important;
}
.zenback-socialbar div.zenback-nodesign div.zenback-socialbar-facebook iframe {
  margin: 0px !important;
  padding: 0px !important;
}
 
 
/*
zenback-news
----------------------------------------------------------------------*/
.zenback #zenback-news{
	display: block;
  clear:both;
  margin-bottom:12px;
	margin-top:12px;
}
.zenback #zenback-news .news-title {
	padding-bottom: 5px;
}
.zenback #zenback-news .newstitle{
	position:absolute;
	top:-24px;
	left:-3px;
}
* html .zenback #zenback-news .newstitle{
	top:-21px
}
*+html .zenback #zenback-news .newstitle{
	top:-21px;
}
.zenback #zenback-news .zenback-newslist{
	padding:5px 10px;
	margin:0;
}
.zenback #zenback-news .zenback-newslist .zenback-newsitem{
	padding:0;
	margin:5px 0;
}
.zenback #zenback-news .zenback-newslist .zenback-newsdate{
	padding:3px 10px 3px 0;
}
.zenback #zenback-news .zenback-newslist .zenback-newsdate img {
	vertical-align: middle !important;
	margin: 0px !important;
	padding: 0px !important;
}
.zenback #zenback-news .newstitle-label {
	position:absolute;
	top:-26px;
	left:82px;
}
.zenback .newstitle-label span {
  font-size: 14px;
  color: #7FC527 !important;
  line-height: 170% !important;
  font-weight: bold !important;
  vertical-align: middle !important;
  display: block !important;
  float: left !important;
}
* html .zenback #zenback-news .newstitle-label {
	top:-100px
}
*+html .zenback #zenback-news .newstitle-label {
	top: -100px
}
#zenback-ads {
  text-align: left !important;
  border:3px solid #80c527 !important;
	border-radius:5px !important;
	-webkit-border-radius:5px !important;
	-moz-border-radius:5px !important;
}
#zenback-ads .ads-tweet-content {
  padding: 10px 5px 10px 5px !important;
  margin: 0 !important;
  min-height: 48px !important;
}
#zenback-ads p {
  padding: 5px !important;
  margin: 0 !important;
  display: block !important;
}
#zenback-ads p span.metadata {
  border-top: 1px solid #e6e6e6 !important;
  display: block !important;
  width: 100% !important;
  clear: both !important;
  margin-top: 8px !important;
  padding-top: 8px !important;
  height: 40px !important;
}
#zenback-ads p span.metadata span.author {
  font-size: 13px !important;
}
#zenback-ads p span.account {
  font-weight: bold !important;
  margin: 10px !important;
}
#zenback-ads p span.metadata span.author a {
  line-height: 22px !important;
  font-size: 20px !important;
  padding: 0px !important;
  text-decoration: none !important;
}
#zenback-ads p span.metadata span.author img {
  float: left !important;
  margin: 0 10px 0 0px !important;
  width: 48px !important;
  height: 48px !important;
}
#zenback-ads a {
  text-decoration: none !important;
}
#zenback-ads a:hover {
  text-decoration: underline !important;
}
#zenback-ads .link a {
  text-decoration: underline !important;
}
#zenback-ads p span.line{
  border-top: 1px solid #fff !important;
  border-top: 1px solid #e6e6e6 !important;
}
/*
zenback customize style
----------------------------------------------------------------------*/
/* module background color */
.zenback {
}
/* title color */
.zenback .zenback-heading span, .zenback .zenback-powered span {
	color:#ffffff;
}
/* title background color */
.zenback .zenback-heading {
	border-bottom-color:#7fc527;
}
.zenback .zenback-entries .zenback-heading span,
.zenback .zenback-entries .zenback-heading {
	background-color: #7fc527;
}
.zenback .zenback-entries ul {
	border-top-color:#7fc527;
}
.zenback .zenback-links .zenback-heading span,
.zenback .zenback-links .zenback-heading {
	background-color:#b0c906;
}
.zenback .zenback-links ul {
	border-top-color:#b0c906;
}
.zenback .zenback-keywords .zenback-heading span,
.zenback .zenback-keywords .zenback-heading {
	background-color:#C58E68;
}
.zenback .zenback-keywords ul {
	border-top-color:#C58E68;
}
.zenback .zenback-facebook .zenback-heading span,
.zenback .zenback-facebook .zenback-heading {
	background-color:#3B5998;
}
.zenback .zenback-facebook ul {
	border-top-color:#3B5998;
}
.zenback .zenback-powered {
	border-top-color:#7fc527;}
.zenback .zenback-heading span, .zenback .zenback-powered span {
	background-color:#7fc527;}
</style>




3:HTML


<div class="zenback" style="display:none;">
      <div class="zenback-socialbar zenback-module">
        <div class="zenback-socialbtn zenback-nodesign">
            <div class="zenback-socialbar-twitter">
                <a href="http://twitter.com/share" class="twitter-share-button" data-url="http://nanoappli.com/blog/" data-count="none" data-via="zenback" data-lang="en">Tweet</a>
                <div class="zenback-socialbar-twitter-count-element">
                  <i></i>
                  <u></u>
                  <span type="button">0</span>
                </div>
            </div>
 
            <div class="zenback-socialbar-hatebu">
                <a href="http://b.hatena.ne.jp/entry/http://nanoappli.com/blog/" class="hatena-bookmark-button" data-hatena-bookmark-layout="standard" title="このエントリーをはてなブックマークに追加"><img src="http://b.st-hatena.com/images/entry-button/button-only.gif" alt="このエントリーをはてなブックマークに追加" width="20" height="20" style="border: none;" /></a>
            </div>
 
            <div class="zenback-socialbar-google-plusone">
                <div id="zenback-google-plusone"></div>
            </div>
 
        </div>
    </div>
  <div class="zenback-entries zenback-module">
    <div class="zenback-heading"><span>関連記事</span></div>
    <ul class="zenback-list">
            <li><a href="http://nanoappli.com/blog/archives/123">記事タイトル... « nanoblog</a></li>
            <li><a href="http://nanoappli.com/blog/archives/50">記事タイトル... « nanoblog</a></li>
            <li><a href="http://nanoappli.com/blog/archives/94">記事タイトル... « nanoblog</a></li>
            <li><a href="http://nanoappli.com/blog/archives/163">記事タイトル... « nanoblog</a></li>
            <li><a href="http://nanoappli.com/blog/archives/270">記事タイトル... « nanoblog</a></li>
          </ul>
  </div>
  <div class="zenback-links zenback-module" style="display:block !important; height:auto !important; visibility:visible !important;">
    <div class="zenback-heading"><span>関連リンク</span></div>
    <ul class="zenback-list">
          <li><a href="http://blog.yoshitomo.org/archives/364" target="_blank">AdSence のクリックできる部分が変更</a></li>
          <li><a href="http://veadardiary.blog29.fc2.com/blog-entry-2445.html" target="_blank">起動しているアプリのアイコンがメニューバーに表示され、クリックで最前面に出すアプリを変更するアプリケ...</a></li>
          <li><a href="http://www.hattrick-supporter.com/archives/51266036.html" target="_blank">掲示板をちょっと変更</a></li>
          <li><a href="http://d.hatena.ne.jp/orangeclover/20091213/1260705242" target="_blank">Google Analyticsのクリック表示が消えない - みちしるべ</a></li>
          <li><a href="http://www.hide10.com/archives/3211" target="_blank">クリックで拡大表示</a></li>
          </ul>
  </div>
                    <div class="zenback-keywords zenback-module" style="display:block !important; height:auto !important; visibility:visible !important;">
    <div class="zenback-heading"><a href="http://zenback.itmedia.co.jp/" target="_blank"><span>関連キーワード</span></a></div>
    <ul class="zenback-list">
                      <li><a href="http://zenback.itmedia.co.jp/keywords/クリック/" target="_blank">クリック</a></li>
                              <li><a href="http://zenback.itmedia.co.jp/keywords/掲示板/" target="_blank">掲示板</a></li>
                              <li><a href="http://zenback.itmedia.co.jp/keywords/フォーラム/" target="_blank">フォーラム</a></li>
                              <li><a href="http://zenback.itmedia.co.jp/keywords/ページ/" target="_blank">ページ</a></li>
                              <li><a href="http://zenback.itmedia.co.jp/keywords/インストール/" target="_blank">インストール</a></li>
                                                                                                                                                                                                                                                                                                                                                                                </ul>
  </div>
        <div id="zenback-news" style="display:block !important; height:auto !important; visibility:visible !important;">
  <div class="news-title">
    <a href="http://blog.zenback.jp/2011/10/classified.html" target="_blank">
      <img src="http://widget.zenback.jp/_p/images/classified.png?v=8a087" alt="zenback">
    </a>
  </div>
  <div id="zenback-ads">
    <div class="ads-tweet-content">
      <div class="components-middle">
        <p>
          <span class="tweet_body">
          </span>
        </p>
      </div>
    </div>
  </div>
</div>
<br />
 
<div class="zenback-powered">
  <span>
    <a href="http://sixapart.jp/zenback/" target="_blank">
      <img src="http://widget.zenback.jp/_p/images/poweredby.gif?v=8eb9c" alt="Powered by zenback" />
    </a>
  </span>
</div>
 
</div>
 
<div id="zenback-script-container">
</div>




4:script本編


<script type="text/javascript">
  var _gaq = _gaq || [];
  (function () {
    var zb_escape_html = function (str) {
      str = str.replace("&","&amp;");
      str = str.replace("\"","&quot;");
      str = str.replace("'","&#039;");
      str = str.replace("<","&lt;");
      str = str.replace(">","&gt;");
      return str;
    };
 
    var zb_load_script = function (options) {
      var script = document.createElement('script');
      script.src = options.url;
      script.type = 'text/javascript';
      script.charset = 'utf-8';
      if (options.async) {
        script.async = true;
      }
      if (window.ActiveXObject) { // for IE
        script.onreadystatechange = function () {
          if (this.readyState === 'onloaded' || this.readyState === 'complete') {
            options.onload && options.onload();
          }
        };
      } else { // for other
        script.onloaddone = false; // for opera
        script.onload = function () {
          if (!this.onloaddone) {
            options.onload && options.onload();
            this.onloaddone = true;
          }
        };
      }
      if (options.element) {
        var s = options.element;
        s.appendChild(script);
      } else { // default insert
        var s = document.getElementsByTagName('script')[0];
        s.parentNode.insertBefore(script, s);
      }
    };
 
    var _zb_print_error = function (msg) {
      window.console && console.error(msg);
    };
 
    var zb_load_jsonp = function (params, callback) {
      if (!params || !callback) {
        throw new Error('Invalid Parameter');
      }
      try {
        var cb_key = params.callbackKey || 'callback';
        var cb_value = params.callbackValue || 'callback';
        var url = (params.url + (params.url.match(/\?/) ? '&' : '?'));
        url += (cb_key + '=' + cb_value);
        var frame = document.createElement("iframe");
        frame.style.display = "none";
        document.body.appendChild(frame);
        var doc = frame.contentWindow.document;
        var count = 0; // for Opera
        frame[frame.readyState/*IE*/ ? "onreadystatechange" : "onload"] = function () {
          if (this.readyState && this.readyState !== 'complete' || count++) {
            return;
          }
          if (doc['__zb_jsonp__']) {
            callback(null, doc['__zb_jsonp__']);
          } else if (params.retry && params.retry >= 1) {
            var timeout = params.timeout || 1000;
            setTimeout(function () {
              zb_load_jsonp({
                url: params.url
                ,callbackKey: cb_key
                ,callbackValue: cb_value
                ,retry: params.retry - 1
                ,timeout: timeout 
              }, callback)}, timeout);
          } else {
            callback({
              message: 'Failed load jsonp'
            });
          }
          setTimeout(function () {
            try {
              frame && frame.parentNode && frame.parentNode.removeChild(frame);
            } catch (e) {
              _zb_print_error(e.message);
            }
          }, 0);
        };
        doc.open();
        doc.write('<' + 'script type="text/javascript">'
            + 'function ' + cb_value + ' (v) { document["__zb_jsonp__"] = v };'
            + '</' + 'script>'
            + '<' + 'script type="text/javascript" src="' + url + '"></' + 'script>');
        doc.close();
        return frame;
      } catch (e) {
        callback(e);
      }
    };
 
    var zb_abort_jsonp = function (frame) {
      try {
        frame && frame.parentNode && frame.parentNode.removeChild(frame);
      } catch (e) {
        _zb_print_error(e.message);
      }
    };
 
    var script_container = document.getElementById('zenback-script-container');
 
    var zb_ga_track_settings = function () {
      _gaq.push(['zb._setAccount', 'UA-17145123-2']);
      _gaq.push(['zb._trackPageview']);
      _gaq.push(['zb._trackEvent', 'widgetNSID', '353091eea229c2955b2271a0c215dd8bcb210a67']);
      _gaq.push(['zb_ads._setAccount', 'UA-17145123-5']);
    };
 
    var zb_ga_track_entries = function () {
      zb_jq('.zenback-entries .zenback-list a').click(function () {
        _gaq.push(['zb._trackEvent', 'kanren_entries', encodeURI(this.href), 'http://nanoappli.com/blog/']);
      });
    };
 
    var zb_ga_track_links = function () {
      zb_jq('.zenback-links .zenback-list a').click(function () {
        _gaq.push(['zb._trackEvent', 'kanren_links', encodeURI(this.href), 'http://nanoappli.com/blog/']);
      });
    };
 
    var zb_ga_track_keywords = function () {
      zb_jq('.zenback-keywords .zenback-list a').click(function () {
        _gaq.push(['zb._trackEvent', 'kanren_keywords', encodeURI(this.href), 'http://nanoappli.com/blog/']);
      });
    };
 
    var zb_ga_track_newsitem = function () {
      zb_jq('.zenback-newsitem a').click(function () {
        _gaq.push(['zb._trackEvent', 'zenback_news', encodeURI(this.href), 'http://nanoappli.com/blog/']);
      });
    };
 
    var zb_ga_track_twitter_widget = function () {
      zb_jq('.zenback-twitter .zenback-list a').click(function() {
        _gaq.push(['zb._trackEvent', 'twitter', encodeURI(this.href), 'http://nanoappli.com/blog/']);
      });
    };
 
    var zb_ga_track_mixi = function () {
      zb_jq('.zenback-socialbar .zenback-socialbar-mixicheck a').click(function () {
        _gaq.push(['zb._trackEvent', 'mixi', encodeURI(this.href), 'http://nanoappli.com/blog/']);
      });
    };
 
    var zb_ga_track_evernote = function () {
      zb_jq('.zenback-socialbar .zenback-socialbar-evernote a').click(function () {
        _gaq.push(['zb._trackEvent', 'evernote', encodeURI(this.href), 'http://nanoappli.com/blog/']);
      });
    };
 
    var zb_ga_track_hatena_bookmark = function () {
      _gaq.push(['zb._trackEvent', 'hatena_bookmark', encodeURI(this.href), 'http://nanoappli.com/blog/']);
    };
 
    var zb_ga_track_googleplusone = function (obj) {
      _gaq.push(['zb._trackSocial', 'google+1', obj.state, obj.href]);
    };
 
    var zb_ga_track_twitter_tweet = function (url) {
      _gaq.push(['zb._trackSocial', 'twitter', 'tweet', url]);
    };
 
    var zb_ga_track_twitter_follow = function () {
      _gaq.push(['zb._trackSocial', 'twitter', 'follow']);
    };
 
 
    var zb_ga_track_fb_comments = function (comment) {
      _gaq.push(['zb._trackSocial', 'facebook', 'comment', comment.href]);
    };
 
    var zb_ga_track_fb_uncomments = function (comment) {
      _gaq.push(['zb._trackSocial', 'facebook', 'uncomment', comment.href]);
    };
 
    zb_load_script({
      url: ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
      ,async: true
      ,onload: function () {
        zb_ga_track_settings();
        zb_ga_track_entries();
        zb_ga_track_links();
        zb_ga_track_newsitem();
        zb_ga_track_keywords();
        zb_ga_track_twitter_widget();
        zb_ga_track_mixi();
        zb_ga_track_evernote();
      }
    });
 
    zb_load_script({
      url: 'http://platform.twitter.com/widgets.js'
      ,element: script_container
      ,async: true
      ,onload: function () {
        var zb_extract_param_from_uri = function (uri, paramName) {
          if (!uri) {
            return '';
          }
          var uri = uri.split('#')[0];  // Remove anchor.
          var parts = uri.split('?');  // Check for query params.
          if (parts.length == 1) {
            return '';
          }
          var query = decodeURI(parts[1]);
 
          // Find url param.
          paramName += '=';
          var params = query.split('&');
          for (var i = 0, param; param = params[i]; ++i) {
            if (param.indexOf(paramName) === 0) {
              return unescape(param.split('=')[1]);
            }
          }
        };
        if (window.twttr) {
          window.twttr.events.bind('tweet', function (event) {
            if (event) {
              var targetUrl;
              if (event.target && event.target.nodeName == 'IFRAME') {
                targetUrl = zb_extract_param_from_uri(event.target.src, 'url');
              }
              zb_ga_track_twitter_tweet(targetUrl);
            }
          });
          window.twttr.events.bind('follow', function (event) {
            event && zb_ga_track_twitter_follow();
          });
        }
      }
    });
 
    zb_load_script({
      url: 'http://b.st-hatena.com/js/bookmark_button.js'
      ,element: script_container
      ,async: true
      ,onload: function () {
      }
    });
 
    window.___gcfg = {
      lang: 'ja'
      ,parsetags: 'explicit'
    };
    zb_load_script({
      url: 'https://apis.google.com/js/plusone.js'
      ,element: script_container
      ,async: true
      ,onload: function () {
        gapi.plusone.render('zenback-google-plusone', {
          'size': 'medium'
          ,'count': 'true'
          ,'callback': 'zb_ga_track_googleplusone'
        });
      }
    });
 
 
 
 
 
    var zb_render_ad_view = function (params) {
      var result = false;
      try {
        var $zb_ads_body = zb_jq('#zenback-ads span.tweet_body');
        var $zb_ads_text_a = zb_jq('<a></a>')
          .attr('href', params.link_url)
          .attr('target', '_blank')
          .addClass('zenback-ads-ad')
          .click(params.ga_click_tracking_code);
        var $zb_ads_link_span = zb_jq('<span></span>')
          .addClass('link');
        $zb_ads_link_span.append($zb_ads_text_a.clone().text(params.view_url));
        $zb_ads_text_a.text(params.ad_text);
        var $zb_ads_metadata_span = zb_jq('<span></span>')
          .addClass('metadata');
        var $zb_ads_author_span = zb_jq('<span></span>')
          .addClass('author');
        var $zb_ads_twitter_a = zb_jq('<a></a>')
          .attr('href', params.twitter_url)
          .attr('target', '_blank')
          .addClass('zenback-ads-twitter')
          .click(params.ga_click_tracking_code);
        var $zb_ads_twitter_a_clone = $zb_ads_twitter_a.clone()
          .text('@' + params.twtiter_screen_name);
        var $zb_ads_img = zb_jq('<img />')
          .attr('src', params.twitter_icon_url)
          .attr('alt', params.twtiter_screen_name);
        $zb_ads_twitter_a.append($zb_ads_img);
        var $zb_ads_strong = zb_jq('<strong></strong>')
          .append($zb_ads_twitter_a_clone);
        $zb_ads_author_span.append($zb_ads_twitter_a)
          .append($zb_ads_strong)
          .append(zb_jq('<br />'))
          .append(zb_jq('<span></span>').text(params.twtiter_screen_name));
        $zb_ads_metadata_span.append($zb_ads_author_span);
        $zb_ads_body.append($zb_ads_text_a)
          .append(zb_jq('<br />'))
          .append($zb_ads_link_span)
          .append($zb_ads_metadata_span)
          .show();
        result = true;
      } catch (e) {
        _zb_print_error(e.message);
      }
      return result;
    };
 
    var zb_show_default_ad_view = function (ga_category) {
      var ga_category = ga_category || 'default';
      var link_url = 'http://zenback.jp/?s=ads_fl01';
      var view_url = 'http://zenback.jp';
      var result = zb_render_ad_view({
        link_url: link_url
        ,view_url: view_url
        ,ad_text: 'zenbackは自分のブログの記事と、過去の自分の記事、他のブログ記事、TwitterやFacebookなどソーシャルメディアをつなげるウィジェットです。多くのブログサービスに対応。設置はコードをコピペするだけ、5分で完了します。ご利用は無料です。'
        ,twitter_url: 'http://twitter.com/zenback'
        ,twitter_icon_url: 'http://a1.twimg.com/profile_images/1185414381/zenback_icon_normal.png'
        ,twtiter_screen_name: 'zenback'
        ,ga_click_tracking_code: function () {
          _gaq.push(['zb_ads._trackEvent', ga_category, 'click', link_url]);
        }
      });
      if (result) {
        _gaq.push(['zb_ads._trackPageview', ga_category, link_url]);
      } else {
        _zb_print_error('Failed the default ad rendering.');
      }
    };
 
 
    var zb_load_twitter_favorites = function (twitter_id, load_count, callback) {
      try {
        var retry = 4;
        var favorites_api_base_url = 'https://api.twitter.com/1/favorites.json';
        var favorites_api_call_url = favorites_api_base_url + '?id=' + twitter_id + '&count=' + load_count;
        zb_load_jsonp({ url: favorites_api_call_url, retry: retry }, function (err, jsonp) {
          if (err) {
            callback(err);
            return;
          }
          callback(null, jsonp);
        });
      } catch (e) {
        callback(e);
      }
    };
 
    var zb_select_ad_tweet = function (free_tweets, filler_tweets, callback) {
      try {
        if (!free_tweets || !filler_tweets) {
          callback({
            message: 'Invalid parameter'
          }, null);
          return;
        }
        var timestamp = (+new Date());
        var select_index = timestamp % 40;
        if (select_index < free_tweets.length) {
          callback(null, {
            ad: free_tweets[select_index]
            ,ga_category: 'zbcf'
          });
        } else {
          if (filler_tweets.length === 0) {
            callback({
              message: 'Nothing filler'
            }, null);
          } else {
            callback(null, {
              ad: filler_tweets[timestamp % filler_tweets.length]
              ,ga_category: 'zbcf2'
            });
          }
        }
      } catch (e) {
        _zb_print_error(e.message);
        callback(e);
      }
    };
 
    var zb_show_ad_view = function (tweet, ga_category) {
      if (!tweet) {
        return;
      }
      var link_url = 'https://twitter.com/#!/' + tweet.user.screen_name + '/status/' + tweet.id_str;
      var view_url = link_url;
      var url_regex = /((?:https?):\/\/[!-~]+)/m;
      if (tweet.text.match(url_regex)) {
        link_url = RegExp.$1;
        view_url = RegExp.$1;
      }
      var twitter_url = 'http://twitter.com/' + tweet.user.screen_name;
      var result = zb_render_ad_view({
        link_url: link_url
        ,view_url: view_url
        ,ad_text: tweet.text
        ,twitter_url: twitter_url
        ,twitter_icon_url: tweet.user.profile_image_url
        ,twtiter_screen_name: tweet.user.screen_name
        ,ga_click_tracking_code: function () {
          _gaq.push(['zb_ads._trackEvent', ga_category, 'click', tweet.id_str]);
        }
      });
      if (result) {
        _gaq.push(['zb_ads._trackPageview', ga_category, tweet.id_str]);
      } else {
        _zb_print_error('Failed the free ad rendering.');
      }
    };
 
    try {
      var ads_free_id = 'zbcf';
      var ads_free_load_number = 40;
      zb_load_twitter_favorites(ads_free_id, ads_free_load_number, function (err, data) {
        if (err) {
          _zb_print_error(err.message);
          zb_show_default_ad_view();
          return;
        }
        var ads_free_tweets = data; 
        var ads_filler_id = 'zbcf2';
        var ads_filler_load_number = 40;
        zb_load_twitter_favorites(ads_filler_id, ads_filler_load_number, function (err, data) {
          if (err) {
            _zb_print_error(err.message);
            zb_show_default_ad_view();
            return;
          }
          var ads_filler_tweets = data;
          zb_select_ad_tweet(ads_free_tweets, ads_filler_tweets, function (err, data) {
            if (err) {
              _zb_print_error(err.message);
              zb_show_default_ad_view();
              return;
            }
            zb_show_ad_view(data.ad, data.ga_category);
          });
        });
      });
 
    } catch (e) {
      _zb_print_error(e.message);
      zb_show_default_ad_view();
    }
 
 
 
  })();
</script>




CSS長いよ。あと、javascriptもぱっと見でコードの重複が多いし。

本当はこのまま解析を続けようかと思ったのですが、余りに長くなってしまったので次回に続きます…

zenbackのスクリプトを解析してみた(1/3)


ブログパーツを作成してみたいのですが、手始めに勉強としてzenbackの動作原理を解析してみました。


まず、入り口です。
サイトにて提示されているスクリプトは以下の通りです。xxxの部分にはサイトに応じた値が入ります。
(実際は1行ですが見辛いので適当に改行してます。)

<!-- X:S ZenBackWidget -->
<script type="text/javascript">
	document.write( unescape("%3Cscript") + 
                    " src='http://widget.zenback.jp/?base_uri=http%3A//xxx.com/blog&nsid=xxx%3A%3Axxx&rand=" + 
                    Math.ceil((new Date()*1)*Math.random()) + 
                    "' type='text/javascript'"+unescape("%3E%3C/script%3E") );
</script>
<!-- X:E ZenBackWidget -->



処理の内容は、以下のURLで取得できるjavascriptを実行しているだけです。

http://widget.zenback.jp/?base_uri=http://nanoappli.com/blog&nsid=101368938457261269::A1013689596
63646496&rand=1





ですので、次は、ブラウザから上記ページにアクセスして、スクリプトの中身を取得します。

document.write( "\n<div id=\"zenback_loady\" style=\"clear:both;background:#ddf2c3;color:#565656;m
argin:0 0 10px 0;padding:5px 0;text-align:center;font-size:13px;display:block !important;\">zenbac
k\u8aad\u307f\u8fbc\u307f\u4e2d\u3067\u3059\u3002</div>\n<script type=\"text/javascript\">\n  wind
ow['zb_$'] = window['$'] || 0;\n</script>\n<script type=\"text/javascript\" src=\"http://ajax.goog
leapis.com/ajax/libs/jquery/1.4.2/jquery.min.js\"></script>\n<script type=\"text/javascript\">\nva
r bootLoaded = 0;\nfunction bootLoader() {\n  if (!window.jQuery) {\n    setTimeout(function() {\n
      bootLoader();\n    }, 200);\n    return;\n  }\n  if (bootLoaded) return;\n  bootLoaded = 1;\
n  window.zb_jq = jQuery.noConflict(true);\n  if (window['zb_$']) { window['$'] = window['zb_$']; }
\n  \n  window.zb_canonical = zb_jq('head link[rel=\"canonical\"]');\n  window.zb_jq(function(){\n
    window.zb_ping = function() {\n      window.zb_loady_zone_width = zb_jq('#zenback_loady').widt
h();\n      zb_jq.ajax({\n        url: \"http://widget.zenback.jp/\"\n        ,dataType:'jsonp'\n
        ,data: zb_jq.extend({\"base_uri\": \"http://nanoappli.com/blog\", \"rand\": \"1\", \"nsid\":
 \"101368938457261269::A101368959663646496\", \"stage\": \"1\"}, {'motouri':document.referrer}, (wi
ndow.zb_canonical.length ? {'canonical_uri':window.zb_canonical.attr('href')} : undefined))\n      
  ,success: function(data) {\n          zb_jq('#zenback_loady').replaceWith(data);\n          zb_jq
('.zenback').fadeIn(200)\n        }\n      });\n    };\n    var $window = zb_jq(window);\n    var d
ummypos = zb_jq('#zenback_loady').position().top;\n    window.zb_pcc = 0;\n    window.zb_sf = funct
ion(){\n      if (window.zb_pcc || ($window.scrollTop()+$window.height()) < dummypos) return;\n    
  window.zb_pcc = 1;\n      window.zb_ping();\n    };\n    zb_jq(window).scroll(window.zb_sf);\n   
 window.zb_sf();\n  });\n};bootLoader();\n</script>\n<iframe frameborder=\"0\" noresize=\"noresize\
" width=\"0\" height=\"0\" src=\"http://widget.zenback.jp/_p/tracking.html?v=cf1d3\"></iframe>\n");


…長すぎて理解できない。
最初のdocument.writeをalertに変更し、スクリプトの実行結果だけ頂くことにします。

取得した結果は以下の通り。

<div id="zenback_loady" style="clear:both;background:#ddf2c3;color:#565656;margin:0 0 10px 0;padding:5px 0;text-align:center;font-size:13px;display:block !important;">zenback読み込み中です。</div>
<script type="text/javascript">
  window['zb_$'] = window['$'] || 0;
</script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
var bootLoaded = 0;
function bootLoader() {
  if (!window.jQuery) {
    setTimeout(function() {
      bootLoader();
    }, 200);
    return;
  }
  if (bootLoaded) return;
  bootLoaded = 1;
  window.zb_jq = jQuery.noConflict(true);
  if (window['zb_$']) { window['$'] = window['zb_$']; }
 
  window.zb_canonical = zb_jq('head link[rel="canonical"]');
  window.zb_jq(function(){
    window.zb_ping = function() {
      window.zb_loady_zone_width = zb_jq('#zenback_loady').width();
      zb_jq.ajax({
        url: "http://widget.zenback.jp/"
        ,dataType:'jsonp'
        ,data: zb_jq.extend({"base_uri": "http://nanoappli.com/blog", "rand": "1", "nsid": "101368938457261269::A101368959663646496", "stage": "1"}, {'motouri':document.referrer}, (window.zb_canonical.length ? {'canonical_uri':window.zb_canonical.attr('href')} : undefined))
        ,success: function(data) {
          zb_jq('#zenback_loady').replaceWith(data);
          zb_jq('.zenback').fadeIn(200)
        }
      });
    };
    var $window = zb_jq(window);
    var dummypos = zb_jq('#zenback_loady').position().top;
    window.zb_pcc = 0;
    window.zb_sf = function(){
      if (window.zb_pcc || ($window.scrollTop()+$window.height()) < dummypos) return;
      window.zb_pcc = 1;
      window.zb_ping();
    };
    zb_jq(window).scroll(window.zb_sf);
    window.zb_sf();
  });
};bootLoader();
</script>
<iframe frameborder="0" noresize="noresize" width="0" height="0" src="http://widget.zenback.jp/_p/tracking.html?v=cf1d3"></iframe>



エスケープされているのが処理されたのと、改行が入ったのでだいぶ見やすくなりましたが、処理がちょっと長いのでコメントを入れつつ個々の記述を理解していきます(一部、説明のために改行を入れてます)。

//----------------------------------------------------------
// ブログパーツが作られるエリアのdivタグを定義
//----------------------------------------------------------
<div id="zenback_loady" style="clear:both;
                               background:#ddf2c3;
                               color:#565656;
                               margin:0 0 10px 0;
                               padding:5px 0;
                               text-align:center;
                               font-size:13px;
                               display:block !important;">
zenback読み込み中です。
</div>
 
<script type="text/javascript">
  //----------------------------------------------------------
  // 既にjQueryのオブジェクトがある場合は、zb_$に退避しておく
  //----------------------------------------------------------
  window['zb_$'] = window['$'] || 0;
</script>
 
<!-- jQueryのライブラリをロードする -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
 
<script type="text/javascript">
//---------------------------------------
// グローバル変数のbootLoadedを定義する
//---------------------------------------
var bootLoaded = 0;
 
//---------------------------------------
// zenbackのロード処理
//---------------------------------------
function bootLoader() {
 
  //---------------------------------------------------------------
  // まだjQueryのロードが終わってなければ、0.2秒待ってからリトライ
  //---------------------------------------------------------------
  if (!window.jQuery) {
    setTimeout(function() {
      bootLoader();
    }, 200);
    return;
  }
 
  //---------------------------------------------------------------------
  // 多重実行防止を行う(zenbackのブログパーツが複数存在したときの対策??)
  //---------------------------------------------------------------------
  if (bootLoaded) return;
  bootLoaded = 1;
 
  //-------------------------------------------------------------------------------------
  // グローバル変数「zb_jq」にjQueryオブジェクトを格納し,zb_$に退避していた場合は元に戻す
  //-------------------------------------------------------------------------------------
  window.zb_jq = jQuery.noConflict(true);
  if (window['zb_$']) { window['$'] = window['zb_$']; }
 
  //-------------------------------------------------------------------------------------
  // グローバル変数「zb_canonical」を用意し...
  // headタグ内に、<link rel="canonical">なタグがある場合は、そのタグを保存する
  //-------------------------------------------------------------------------------------
  window.zb_canonical = zb_jq('head link[rel="canonical"]');
 
  //-------------------------------------------------
  // ページのロードが完了したとき、以下の処理を行う
  // (処理内容は後述)
  //-------------------------------------------------
  window.zb_jq(function(){
    window.zb_ping = function() {
      window.zb_loady_zone_width = zb_jq('#zenback_loady').width();
      zb_jq.ajax({
        url: "http://widget.zenback.jp/"
        ,dataType:'jsonp'
        ,data: zb_jq.extend({"base_uri": "http://nanoappli.com/blog", "rand": "1", "nsid": "101368938457261269::A101368959663646496", "stage": "1"}, {'motouri':document.referrer}, (window.zb_canonical.length ? {'canonical_uri':window.zb_canonical.attr('href')} : undefined))
        ,success: function(data) {
          zb_jq('#zenback_loady').replaceWith(data);
          zb_jq('.zenback').fadeIn(200)
        }
      });
    };
    var $window = zb_jq(window);
    var dummypos = zb_jq('#zenback_loady').position().top;
    window.zb_pcc = 0;
    window.zb_sf = function(){
      if (window.zb_pcc || ($window.scrollTop()+$window.height()) < dummypos) return;
      window.zb_pcc = 1;
      window.zb_ping();
    };
    zb_jq(window).scroll(window.zb_sf);
    window.zb_sf();
  });
};
//-------------------------------------
// 上で定義したロード処理をコールする
//-------------------------------------
bootLoader();
</script>
 
 
//-------------------------------------------------
// Google Analysticsを使用して、アクセスログを取る
// (下記のURLにアクセスすると分かります)
//-------------------------------------------------
<iframe frameborder="0" noresize="noresize" width="0" height="0" src="http://widget.zenback.jp/_p/tracking.html?v=cf1d3"></iframe>


なんだかグローバルな変数が多いなぁ…という印象。
でも、大きな流れは理解できました。

次は、本編(?)であるwindow.zb_jq関数の中身に入って行きます。zb_jqにはjQueryのオブジェクトが入っているので、これは、普通のスクリプトだといわゆる “$.ready(“に相当するものです。

で、中身です。こっちもコメントを入れていきます。

window.zb_jq(function(){
  //----------------------------------------
  // 関数の定義 : zb_ping()
  // ブログパーツのhtmlをロードする
  // これもグローバルですか...
  //----------------------------------------
  window.zb_ping = function() {
    //---------------------------------------------------------------
    // パーツの横幅を取得し、グローバル変数zb_loady_zone_widthに保存
    //---------------------------------------------------------------
    window.zb_loady_zone_width = zb_jq('#zenback_loady').width();
    //------------------------------------------
    // 非同期で,パーツ内のhtmlを取得する
    // ちょっと複雑なので、詳細は後述します...
    //------------------------------------------
    zb_jq.ajax({
      url: "http://widget.zenback.jp/"
      ,dataType:'jsonp'
      ,data: zb_jq.extend({"base_uri": "http://nanoappli.com/blog", "rand": "1", "nsid": "101368938457261269::A101368959663646496", "stage": "1"}, {'motouri':document.referrer}, (window.zb_canonical.length ? {'canonical_uri':window.zb_canonical.attr('href')} : undefined))
      ,success: function(data) {
        zb_jq('#zenback_loady').replaceWith(data);
        zb_jq('.zenback').fadeIn(200)
      }
    });
  };
 
  // windowオブジェクトを取得する
  var $window = zb_jq(window);
  // パーツのある場所(ページ上部からの高さ)を求める
  var dummypos = zb_jq('#zenback_loady').position().top;
  // パーツのhtmlを読み込んだかflgを、"0:未読み込み"で初期化
  window.zb_pcc = 0;
 
  //------------------------------------------------------------------------
  // 関数の定義: zb_sf()
  // ブログパーツがある所までページがスクロールされてたらパーツをロードする
  //------------------------------------------------------------------------
  window.zb_sf = function(){
  // 既にロード済みだったり,画面のスクロール位置がパーツのある所まで来てない時は何もしない
    if (window.zb_pcc || ($window.scrollTop()+$window.height()) < dummypos) return;
    window.zb_pcc = 1;
 
    // パーツのhtmlを読み込む
    window.zb_ping();
  };
 
  //-----------------------------------------------------------
  // ブラウザでページがスクロールした時のイベントハンドラとして
 // zb_sf() を登録する(ブログパーツ本体の読込処理)
  //-----------------------------------------------------------
  zb_jq(window).scroll(window.zb_sf);
 
  //-----------------------------------------------------------
  // ハンドラに登録した上で、直ぐに一回だけコールする
  // ページ表示のタイミングでロードが必要な場合もあるので
  //-----------------------------------------------------------
  window.zb_sf();
});



最後に、ajaxによるページの非同期読み込み処理を調査します。
1行が長くて読み辛いので、かなり改行を入れてます。

zb_jq.ajax({
  url: "http://widget.zenback.jp/"
  ,dataType:'jsonp'
  ,data: zb_jq.extend({  "base_uri": "http://nanoappli.com/blog", 
                         "rand"    : "1", 
                         "nsid"    : "101368938457261269::A101368959663646496", 
                         "stage"   : "1"}, 
                      {  'motouri':document.referrer }, 
                      (window.zb_canonical.length ? {'canonical_uri':window.zb_canonical.attr('href')} : undefined))
  ,success: function(data) {
    zb_jq('#zenback_loady').replaceWith(data);
    zb_jq('.zenback').fadeIn(200)
  }




$.ajax()関数で、http://widget.zenback.jp/ へ非同期アクセスを行います。dataTypeは”jsonp”なので、jsonpの呼び出しですがcallback指定が無いので,適当なメソッド名でコールされるようです。(この部分自信なし…)
また、アクセスはGETで行われ、クエリストリングdataで指定されています。具体的には以下のような感じになります。

key           value
------------- --------------------------------------------------------------------
base_uri      http://nanoappli.com/blog
callback    jsonp1328873042994
canonical_uri ページのheadタグ内にあるrel="canonical"タグの中身(=正規化されたURL)
motouri       本ページのアクセス元リファラ
nsid	      101368938457261269::101368959663646496
rand          738535305094
stage         1



アクセスが成功したら、successに記載されている関数がコールされます。
関数内ではid=”zenback_loady”なdivタグの内容を置き換えた上で、その内容を200msecかけてフェードインさせます。



次は、このajax呼び出しに対するレスポンス内容の解析になるのですが…、ここまで来るのにだいぶ掛かったので、続きは次回にします。

JavaScriptをブラウザだけで作成&テスト出来る環境を作った(JS.nanobench)

JavaScriptの動作確認をしているのですが、「エディタで編集 ⇔ ブラウザで確認」の行ったりきたりが面倒なので、ブラウザだけで確認できるようにしました。


作ったもの

これです↓

JS.nanoBench

プログラム:






出力結果:


エラー:



仕組み

仕組みは、textAreaの中身をevalして出力してるだけです。
…だと説明にならないので、html,js,cssのソース見ながら解説して行きます。

html

まずは、htmlの構造です。
特に何の変哲も無いhtmlです。
各パーツにidを振ってあるのと、ボタンのonclickだけ覚えておいて、javascriptのチェックに移りましょう。

<div id="scratchPad">
	<div id="title">JS.nanoBench</div>
 
	<h3>プログラム:</h3>
	<textarea id="txtSrc">console.log( 2 * 3.14 );</textarea>
	<br />
	<input type="submit" id="btnRun"   value="実行"       onclick="runScript();">
	<input type="button" id="btnClear" value="出力クリア" onclick="clearOutput();">
	<br />
 
	<h3>出力結果:</h3>
	<div id="txtOutput"></div>
 
	<h3>エラー:</h3>
	<div id="txtError"></div>
</div>




JavaScript

次はJavaScriptです。

// コンソール出力を乗っ取る
console.log = function(msg){
	document.getElementById('txtOutput').innerHTML += msg + '<br />';
}
 
// スクリプトの実行
var runScript = function() {
	// 出力結果を一旦クリア
	clearOutput();
 
	// 入力されたテキストを実行する
	try {
		var srcStr = document.getElementById( 'txtSrc' ).value;
		eval( srcStr );
		document.getElementById('txtError').innerHTML = 'エラーは有りません';
	}catch( e ){
		document.getElementById('txtError').innerHTML = e;
	}
};
 
// 出力エリアのクリア
var clearOutput = function() {
	document.getElementById( 'txtError'  ).innerHTML = '';
	document.getElementById( 'txtOutput' ).innerHTML = '';
};


関数が三つ定義されてます。

まず2つ目のconsole.log()です。
これは、画面へのメッセージ出力を行っています。
文字出力は、firebugなんかを使用している場合、console.log()を使うことが多いと思うので、このメソッドの振る舞いを直接変更してしまいます。標準の関数を乗っ取ってしまうのは、普通だとお行儀の悪いやり方ですが、動作確認用なので良しとしておきます。
また、この形にしておくと作ったスクリプトをblog等にアップするとき、メッセージ出力の関数を書き換えなくて済むというメリットがあります。


2つ目はrunScript()です。
これは、画面で「実行」ボタンがクリックされたときに走る処理です。
画面の出力・エラーメッセージ欄をクリア後、ソースが書かれているtextareaの中身を取得し、eval()で評価(=実行)を行っています。
またcatch句でエラーが出たときのメッセージ出力を行っています。


最後のclearOutput()は、見ての通り出力結果・エラーメッセージの中身をクリアしています。


css

最後にcssです。
これは、特に見るべきところは無く「適当に装飾してみました」って程度のものです。
横幅を変えたいときは、#scratchPadのwidthを変更するだけで対応可能です。

#scratchPad {
	border:1px solid #ccc;
	padding:10px;
	width:600px;
}
 
#scratchPad #title {
	text-align:center;
	color:#666;
	background-color:#e0e0f0;
	border-bottom:1px solid #aaf;
	width:100%;
	padding:10px;
	margin: -10px -10px 0 -10px;
}
 
#scratchPad h3 {
	font-weight:normal;
	margin-bottom:2px;
}
 
#txtSrc {
	width:98%;
	height:10em;
}
#btnRun, #btnClear {
	padding:10px 20px;
}
 
#txtOutput, #txtError {
	border:1px solid #eee;
	padding:10px;
}
#txtOutput {
	background-color: #eef;
	border:1px solid  #ccf;
	font-family: "MS ゴシック";
}
#txtError {
	background-color: #fee;
	border:1px solid  #fcc;
}





TODO

上記のスクリプトは、「とりあえず30分ぐらいで試作してみました」というレベルなので、もう少しブラッシュアップしておきたい。
とりあえず思いつく限りでは、以下のような所かな…

  • jQuery等のライブラリを、チェックボックスを入れるだけで使えるようにする。
  • 作った関数がグローバルなネームスペースに有るので、もうちょい隠蔽させる。
  • blogパーツ化を目指して、iframeを1行書くだけでOKな形に持って行く。
  • Ctrl-Enterでスクリプト実行等のショートカットキーを作る。
  • エラーが出たときに行番号が出せるとbest!(出来るのかは不明…)。
  • blogへ貼付用として、ボタンを押すとスクリプト&結果をマークアップして出力する。