f8g

Ctrl + Click で検索

テキストボックスを Ctrl + Click すると, 親 form を submit.

// ==UserScript==
// @name           post ctrl click
// @namespace      d.hatena.ne.jp/arikui
// @include        *
// @require        http://gist.github.com/29681.txt
// ==/UserScript==

$X("//input[@type='text' or not(@type)]").forEach(function(element){
	if(element.form) element.addEventListener("click", function(e){
		if(e.ctrlKey)
			element.form.submit();
	}, false);
});

ALC で使おうと思っていたのに上の方のテキストボックスが form の中にないというクソ HTML でダメでした. → input.form ならいけるそうです.

追記

os0xさんがずっとシンプルに書き直したやつ。
http://gist.github.com/219324

クロージャ, call, apply

http://d.hatena.ne.jp/arikui/20090928/1254140257

var o = {r: 1};

1. クロージャ

o.f = function(){
	var self = this;
	(function(n){
		self.r += n;
	})(2);
};

2. call

o.f = function(){
	(function(n){
		this.r += n;
	}).call(this, 2);
};

3. apply (callのやつの単なる置き換え)

o.f = function(){
	(function(n){
		this.r += n;
	}).apply(this, [2]);
};

4. apply (argumentsを使う)

o.f = function(){
	(function(n){
		this.r += n;
	}).apply(this, arguments);
};

// o.f(2);

結果

firefox Chrome IE Opera
1 0.0002546 0.0006892 0.006588 0.002508
2 0.0002748 0.0004598 0.007134 0.00312
3 0.000806 0.000684 0.009875 0.004784
4 0.0056621 0.000458 0.012456 0.004944

Firefox の 4, 5 については結果が安定しなかったので, 〜10%減ぐらいに落ち着くかもしれない. それでも結構差は出るだろう.
全体的な傾向としてはスコープ辿るより call, apply で this を置き換えた方が遅いということになる. ただ, Google Chrome の結果はそれとは逆になっていて面白い.
次のような, 引数の配列を予め用意しておくコード.

o.a = [2];
o.f = function(){
	(function(n){
		this.r += n;
	}).apply(this, this.a);
};
Firefox Chrome
0.0003414 0.000592

あれ, Firefox も速くなってね?みたいなことになる. よく分からないので自分でご確認ください.

http://arikui.github.com/test/scope_call.html

ifとスコープの速さ

ifとor

書き方として気になったんですが、

if(!b) b = 2;
a = b + c;

a = (b || (b = 2)) + c;

と、if文を書くかどうかで速度に違いが出るのかなー、と思って計ってみました。

// case 1
var a, b, c = 1;
while(i--){
	if(!b) b = 2;
	a = b + c;
	b = null;
}

// case 2
var a, b, c = 1;
while(i--){
	a = (b || (b = 2)) + c;
	b = null;
}
fx3.5 ie8 opera10 chrome3.0
case 1 0.00001313 0.0002628 0.0001864 0.00002484
case 2 0.00001188 0.0002962 0.0002122 0.00003132

(単位はms)
どれも体感で差が出るような違いはありませんが、Firefoxだけifの方が遅いみたいですね。

スコープ

Firefoxで計ってて思ったんですが、変数のスコープも速度に影響あるんだなあ、と思ってこっちも計ってみました。

// case 3
var a, b, c = 1;
while(n--) (function(){
	a = (b || (b = 2)) + c;
	b = null;
})();

// case 4
while(n--) (function(){
	var a, b, c = 1;
	a = (b || (b = 2)) + c;
	b = null;
})();
fx3.5 ie8 opera10 chrome
0.003435 0.003586 0.001582 0.0004042
0.0001866 0.002802 0.001396 0.0003785

(単位はms)

どのブラウザでも変数定義よりスコープ辿る方がコスト高いようです。特にFirefoxだと大きな差が。俺のFirefoxどっか悪いんだろうか。

まとめ

  • ifでもorでも速度的に大差なさそうだし好きに書くのがいいです
  • 変数定義のコストを考えるよりスコープチェーンを気にした方がいいです

ソース: http://arikui.github.com/test/if_scope.html

ツリーマップ型の音楽再生機

http://gyazo.com/561a3067f2bd8b7beb476714b74c0ab2.png

最近は Ruby-Processing を使って「ビジュアライジングデータ」をやってます。
GitHub - jashkenas/ruby-processing: Code as Art, Art as Code. Processing and Ruby are meant for each other.

Ruby-Processing のいいところ

  • JRuby で動くので Processing で使うような Java のライブラリが使える
    • 「ビジュアライジングデータ」で利用している Treemap のライブラリも使えます
  • gem install すると JRuby ごとくっついてくるので, わざわざ JRuby を用意する必要がない
  • 「ビジュアライジングデータ」の内容で困ることはあんまりない
    • 後半は rp5 create で --bare 付けると困る
  • Ruby でスケッチを書けるのでオリジナルの Processing より楽しい (個人的に)

Ruby-Processing の嫌なところ

  • JRuby で動くので JRuby のわけわかんないところを調べる必要がある
    • 基本的に JRuby がよくわかんないので困ります

音楽再生機

スケッチから iTunes をコントロールして音楽を再生するやつ。

http://gyazo.com/54eed0597b31d36b3577f867110f53b8.pnghttp://gyazo.com/40f933b692077e4ba4c7a53008d6abd0.png

スケッチから iTunes をコントロールするインタフェースは COM なんですが、JRuby では Win32OLE が使えないみたいなので、コントロールするだけのスクリプトを書いてスケッチから実行させてます。

`cscript "#{exec_file}" "#{artist}" "#{album}" "#{song}"`

他にも、細かい曲のデータなんかも、拡大時にスクリプトを実行して取得しています。(一時的に動作が止まって変な感じになる)

全体的に動作が変な感じですけど、「ビジュアライジングデータ」のコードが訳分からんので仕方ないです。ソースコードGitHub に置いておきましたが、ベースは「ビジュアライジングデータ」の7章と一緒です。
http://github.com/arikui/treemap_player/tree/master

ツリーマップのアルゴリズム自体は JavaScript でも実装できそうなので、Processing.js を使って色々なデータをツリーマップで表現して遊べそうですね。

Rubyで書いてJavaScriptで使う 2(managed codeを使いやすくする)

前回の続き。
http://d.hatena.ne.jp/arikui/20090428/1240866008
前は、

(なんちゃら).Invoke("呼ぶメソッドの名前", [引数])

という風でしたがやっぱり使いづらい。Ruby のオブジェクトをそのまま JavaScript にしたような感じで使いたい。ということで、多少の変更を加えます。

変更点

メソッドをLambdaで登録

http://d.hatena.ne.jp/arikui/20090305/1236259323
Lambda で登録すれば柔軟にオブジェクトが作れるしブラウザ側からメンバが見えます。

引数の渡し方を改善

ブラウザ - Silverlight 間の可変長引数の渡しが狂ってるなら、その辺は JavaScript に任せます。

new Function("return arguments.callee.Invoke(arguments)");

みたいなのを介して呼び出せば、Silverlight 側には常に ScriptObject が引数として渡されるようになります。実際に Managed Code を呼び出してるのは、この Function オブジェクトに登録されてる Invoke メソッドです。

Public Delegate Function F(Of T, TResult)(ByVal args As T) As TResult

Public Function ToScriptObject() As Browser.ScriptObject
    Dim obj As Browser.ScriptObject = window.CreateInstance("Object")
    Dim names As Collections.Generic.IList(Of String) = operations.GetMemberNames(result)
    
    For i = 0 To names.Count - 1
        Dim name As String = names(i)
        Dim fun As F(Of Browser.ScriptObject, Object) = Function(args As Browser.ScriptObject) Invoke(name, args)
        Dim method As Browser.ScriptObject = window.CreateInstance("Function", "return arguments.callee.Invoke(arguments)")
        method.SetProperty("Invoke", fun)
        obj.SetProperty(name, method)
    Next

    Return obj
End Function

デモ

http://arikui.github.com/run_ruby/TestPage.html
見えるぞ、私にもメソッドが見える。
http://gyazo.com/8bce1a2a268e8465bd1f3a1111e3eb0e.png

コード

Public Class ScriptableRuby
    Private Shared window As Browser.HtmlWindow = Browser.HtmlPage.Window
    Private Shared runtime As Microsoft.Scripting.Hosting.ScriptRuntime = IronRuby.Ruby.CreateRuntime
    Private Shared engine As Microsoft.Scripting.Hosting.ScriptEngine = runtime.GetEngine("IronRuby")
    Private Shared scope As Microsoft.Scripting.Hosting.ScriptScope = engine.CreateScope
    Private Shared operations As Microsoft.Scripting.Hosting.ObjectOperations = engine.CreateOperations(scope)

    Public Shared Function Create(ByVal source As String) As RubyObject
        Dim result = engine.Execute(source, scope)
        Return New RubyObject(ScriptableRuby.operations, result)
    End Function

    Public Shared Function Arrayify(ByVal args As Browser.ScriptObject, Optional ByVal arity As Integer = -1) As Object()
        Dim length As Integer = args.GetProperty("length")
        arity = IIf(arity < 0, length, arity)
        Dim _args(arity - 1) As Object

        For i As Integer = 0 To arity - 1
            _args(i) = args.GetProperty(i)
        Next

        Return _args
    End Function

    <Browser.ScriptableType()> _
    Public Class RubyObject
        Private result As Object
        Public Delegate Function F(Of T, TResult)(ByVal args As T) As TResult
        Public Delegate Function F(Of TResult)() As TResult
        
        Public Sub New(ByVal operations As Microsoft.Scripting.Hosting.ObjectOperations, ByVal result As Object)
            Me.result = result
        End Sub

        Public Function ToScriptObject() As Browser.ScriptObject
            Dim obj As Browser.ScriptObject = window.CreateInstance("Object")
            Dim names As Collections.Generic.IList(Of String) = operations.GetMemberNames(result)
            
            For i = 0 To names.Count - 1
                Dim name As String = names(i)
                Dim fun As F(Of Browser.ScriptObject, Object) = Function(args As Browser.ScriptObject) Invoke(name, args)
                Dim method As Browser.ScriptObject = window.CreateInstance("Function", "return arguments.callee.Invoke(arguments)")
                method.SetProperty("Invoke", fun)
                obj.SetProperty(name, method)
            Next

            Return obj
        End Function

        <Browser.ScriptableMember()> _
        Public Function Invoke(ByVal name As Object, Optional ByVal args As Browser.ScriptObject = Nothing) As Object
            Dim member As IronRuby.Builtins.RubyMethod = operations.GetMember(result, name)

            If args Is Nothing Then
                Return engine.Operations.Invoke(member)
            Else
                Return engine.Operations.Invoke(member, ScriptableRuby.Arrayify(args, member.Info.GetArity()))
            End If
        End Function

        <Browser.ScriptableMember()> _
        Public Function Invoke(ByVal name As Object, ByVal ParamArray args As Object()) As Object
            Return engine.Operations.Invoke(operations.GetMember(result, name), args)
        End Function
    End Class
End Class

Rubyで書いてJavaScriptで使う

AgDLRのRepl面白いですね。
CodePlex Archive
ブラウザ上で Ruby を試せる「IRBWEB」を作ってみた - てっく煮ブログ 跡地
Replは名前のとおりブラウザ上でレプるためのパーツって感じでしょうか。Replの実装は大体こんな感じでやってるんだと思います。
http://blog.tomasm.net/2009/02/20/multilingual-repl/
それを参考にして、Rubyで作ったオブジェクトをJavaScriptで利用するサンプル。
http://arikui.github.com/run_ruby/TestPage.html
オブジェクト登録後に

(なんちゃら).Invoke("呼ぶメソッドの名前", [引数])

みたいな感じでFirebugなどから呼び出してみてください。

ソース

arikui.github.com/Page.xaml.vb at 5ed26862d4fd7044ddf2c6057c9146b019459037 · arikui/arikui.github.com · GitHub

Public Class ScriptableRuby
    Private Shared window As Browser.HtmlWindow = Browser.HtmlPage.Window
    Private Shared runtime As Microsoft.Scripting.Hosting.ScriptRuntime = IronRuby.Ruby.CreateRuntime
    Private Shared engine As Microsoft.Scripting.Hosting.ScriptEngine = runtime.GetEngine("IronRuby")
    Private Shared scope As Microsoft.Scripting.Hosting.ScriptScope = engine.CreateScope
    Private Shared operations As Microsoft.Scripting.Hosting.ObjectOperations = engine.CreateOperations(scope)

    Public Shared Function Create(ByVal source As String)
        Dim result = engine.Execute(source, scope)
        Return New RubyObject(ScriptableRuby.operations, result)
    End Function

    <Browser.ScriptableType()> _
    Public Class RubyObject
        Private result As Object

        Public Sub New(ByVal operations As Microsoft.Scripting.Hosting.ObjectOperations, ByVal result As Object)
            Me.result = result
        End Sub

        <Browser.ScriptableMember()> _
        Public Function Invoke(ByVal name As Object) As Object
            Return engine.Operations.Invoke(operations.GetMember(result, name))
        End Function

        <Browser.ScriptableMember()> _
        Public Function Invoke(ByVal name As Object, ByVal args As Browser.ScriptObject) As Object
            Dim length As Integer = args.GetProperty("length")
            Dim _args(length - 1) As Object

            For i As Integer = 0 To length - 1
                _args(i) = args.GetProperty(i)
            Next

            Return engine.Operations.Invoke(operations.GetMember(result, name), _args)
        End Function

        <Browser.ScriptableMember()> _
        Public Function Invoke(ByVal name As Object, ByVal ParamArray args As Object()) As Object
            Return engine.Operations.Invoke(operations.GetMember(result, name), args)
        End Function
    End Class
End Class

ScriptableRuby.Create()は、Rubyのコードを実行して返ってきた値をJavaScriptで利用可能なScriptableRuby.RubyObjectとして返します。返ってくる値はコードの最後の行のものなので、「A.new」とか最後に書いとかないと利用できません。(そうしなくてもいい方法はあるんでしょうかね)
ScriptableRuby.RubyObjectはInvoke()でRubyのオブジェクトのメソッドを呼び出します。(サンプルではメソッドのみしか利用できません。多分)
ここで問題なのは、引数にParamArrayを指定してもJavaScriptSilverlightでちゃんと渡してくれません。なのでJavaScript側で配列として渡すことにしました。実質3つめのInvoke()は使われません。

Text to Canvas

この前のネタ、テキスト手書きとかすると酷いことになるなー、と思ったのでFx3独自仕様を使って文字を描画します。Canvas上をダブルクリック。日本語も可能でした(Windows)。
文字を描く | MDN
座標は適当にやってるので、ちゃんと調整しないとマズイ。

// ==UserScript==
// @name          Text to Canvas
// @namespace     http://d.hatena.ne.jp/arikui/
// @include       *
// ==/UserScript==

var context = null;
var input   = document.body.appendChild(document.createElement("input"));
var offset  = Math.round(input.getBoundingClientRect().height / 2);

input.style.position = "absolute";
input.style.display  = "none";

input.addEventListener("keyup", function(e){
	if(e.keyCode !== 13)
		return;

	var rect = input.getBoundingClientRect();

	context.translate(rect.left, rect.top);
	context.mozDrawText(e.target.value);
	context.fill();
	context.translate(-rect.left, -rect.top);

	input.value = "";
	input.style.display = "none";
}, false);

document.body.addEventListener("dblclick", function(e){
	if(e.target.tagName.toLowerCase() != "canvas")
		return;

	context = e.target.getContext("2d");
	input.focus();

	(function(style){
		style.display = "block";
		style.zIndex  = getComputedStyle(e.target, "").zIndex * 1 + 1 || "9999";
		style.left    = e.clientX + "px";
		style.top     = e.clientY - offset + "px";
	})(input.style);
}, false);

keyword:Firefox独自仕様
keyword:IE独自仕様