iPhone/iPod touch用 JavaScript コンソール

投稿者 nanki 2008-09-09 18:55:00 GMT

iPhone/iPodのキーボードは、まったくもってプログラミングに向かいないけれども、 ちょっとしたJavaScriptを試してみたい時はあるので、どうにかならないか試してみた。

jsconsole for iphone

JavaScript Console for iPhone

若干妙なところもあるけど、一応動く。 ちょっとしたコードなら、思ったよりさくさく?かける。

高度なカーソル移動や、単語境界まで削除、みたいな機能は欲しいなぁ。

やっぱり、touchイベントを使うので、普通のSafariで動かない。めんどい… textareaのスクロールも…

AJAX Libraries API を使って、著名なライブラリをロードできる機能もつけてみた。

http://tools.netswitch.jp/jsconsole/?lib=j,ju こんな感じにすると、jQuery, jQueryUIを読み込んでいる(はず).

j=jQuery, ju=jQuery UI, p=prototype, s=scriptaculous, m=mootools, d=dojo

書き上がったJavaScriptはメールで送信できるらしい。

参考:

iPhone/iPod用Todoリスト

投稿者 nanki 2008-09-04 05:40:00 GMT

動機: MobileSafariはマウス系のイベントが(そのままでは)あんまり動いてくれないために、哀しい思いをする事が多々あるので、一度その辺をちゃんと調べてみたかった。

結果: いい!楽しいものができた。

せっかく作るのだから、タッチセンサを生かしたインターフェースにしたかったので、 まずは Todoリストに必要な本質的な機能を洗い出す。

  • タスク追加
  • 優先順位
  • 完了
  • 削除

タスクの編集機能なんかははあえて省いているし、タスク名しかない。

それらの機能を連続的なインタラクションの中に組み込むことを主眼に構想を練る。

「削除」→「本当に削除しますか」→「OK」なんてのを5回も繰り返すのはいけない。 でも、間違えて削除してしまうようなのもダメ。

Todo list for iPhone
指先一つで一気にタスクが完了する魔法のツール!

html/css/js で動くモックを作って、最後にモデル部分をサーバ側と通信するように調整して完成。

試していただける方はこちらからどうぞ。

Create Accountすると、専用のURLが作成されます。 URL認証なので、そのままブックマークすればOK. URLをそのまま晒して*ジャック計画とかが漏れても自己責任で。

最後にJavaScript的な話をすると、

  • マウスイベントを全部iPhone用のイベントに置換
  • CPUパワーの違いでイベント処理がいっぱいいっぱいになる対策
  • 絶対位置を指定するデバイスなので、マウス用のインタラクションを一部修正

というような点でscriptaculousのdragdrop.js を直接書換えています。 なので、普通のSafariでも動きません!あしからず。

追記: Safari でも動くようにした。

ご意見・感想などはコメント欄へ。

参考:

todo.switch - todo.dotswitch.net


JavaScript デバッグツール - コードゴルフIE杯

投稿者 nanki 2008-06-12 13:31:00 GMT

先ほどのdebug.jsを読み込むBookmarklet.

debug

javascript:void((function(r){var d=document,w=arguments.callee,v,t=r[0],n=r.slice(1);if(t){v=t.split('$');if(v[1]){try{eval(v[0]);t=v[1]}catch(e){setTimeout(function(){w(r)},100);return}}v=t.split('@');if(v[1]){try{eval(v[0]);w(n);return}catch(e){t=v[1]}}d.body.appendChild(d.createElement('script')).src='http://tools.netswitch.jp/jstools/'+t+'.js';w(n)}})(['Prototype@prototype','Prototype$Effect@effects','Effect$DebugTool@debug']))

外部のJavaScriptを読み込むには、JSONPなどでも使われているscriptタグの動的生成を使うのだが、これが非同期で、prototype.js, effect.js, debug.jsを順番に読み込まないといけない今回のような場合はちょっと工夫が必要になる。

effect.jsはPrototype定数が定義されるまで読まない、debug.jsはEffect定数が定義されるまで読まない、など。

それとは別に、すでにprototype.jsが読み込まれている場合は、prototype.jsの読み込みをご遠慮する機能もつけてある。 ブックマークレット中の最後の方の@とか$とかにはさまれているあたりがそれ。 evalして例外が上がらなければ、あるいは上がれば、ロードして、次へ。

しかし、どういうわけか、一番使いたいIEで動かない。 alertを追加するしないで、syntax errorがでたりでなかったりするあたりで、ようやく、bookmarkletの文字数制限を思い出した。

というわけでそこから先は、コードゴルフIE杯。 今では、400台前半に落ち着いて、無事動くようになり、IE6SP2でも余裕がある。

参考:

Rules for Bookmarklets


JavaScript デバッグツール

投稿者 nanki 2008-06-12 13:04:00 GMT

inspection

いろんなブラウザでチェックしているうちに、ちょくちょく使う機能をまとめてみた。

DebugTool.alertbox(msg)
  console.log の代わり。

DebugTool.report(target, filter)
  target内の関数呼び出しを監視。
  内部でprototype.js とかを使いまくってるので、DebugTool.report(Prototype)とかはやらないこと!

DebugTool.inspect(target)
  DOM ツリーを遡って、タグ名、id、class属性などを表示。

DebugTool.shell()
  シェルっぽいのがでてくる。未完成、おまけ。

などなど。

要 prototype.js, effect.js

debug.js

これを任意のページでロードするbookmarkletの話は、別エントリで書く。

reportの中身は結構面白いと思う。

  report: function(target, filter) {
    var func = function(name) {
      return (function(a){
        var result = a.apply((function() {return this}).apply(a), $A(arguments).slice(1));
        DebugTool.alertbox(name + '(' + $A(arguments).slice(1).inspect().gsub(/^\[|\]$/, '') + ') -> '+$A([result]).inspect()+'.');
        return result;
      });
    };

    for (key in target) {
      if (typeof target[key] == 'function' && (!filter || filter(key))) {
        target[key] = target[key].wrap(func(key));
      }
    }
  },

wait もおすすめ。

  wait: function(wait_for, callback) {
    var check = false;
    try{
      check = (typeof(wait_for) == 'function') ? wait_for() : eval(wait_for);
    }catch(e){
    }

    if(check){
      callback(check);
    } else {
      var wait = arguments.callee;
      setTimeout(function(){wait(wait_for, callback)}, 100);
    }
  },

JavaScriptでウィンドウがピタッ!

投稿者 nanki 2008-05-13 03:23:00 GMT

世間では window.moveByの時代がくるとか言われているそうですね。

こちらは、window.moveToだけど…

demo

参考:

window.moveBy の時代がくる、こない - 冬通りに消え行く制服ガールは、夢物語にリアルを求めない。 - subtech


RubyでもJavaScriptでも動くコード

投稿者 nanki 2008-04-30 14:19:00 GMT
"#{"/*"}"
  Ruby Code.
__END__
*/
  JavaScipt Code.
"#{"/*"}"
  Ruby Code.
__END__
*/
  JavaScipt Code.

他にもあると思うけど。

こういうのどうやって探したらいいんだろう。

追記:

たった二つから使っていいのかわからないが、polyglotと呼ぶらしい。


次のページへ - accessNext

投稿者 nanki 2008-04-09 02:50:00 GMT

ページ中の「次へ」とか「前へ」とかのリンクに、accesskeyを設定して、Ctrl-l / Ctrl-hでショートカットできるようにするGreasemonkeyスクリプト。 $X関数は、こちらのを参考(コピペ)に、以下の修正を加えてある。

- var o = document.createNSResolver(context)(prefix);
+ var o = document.createNSResolver(context).lookupNamespaceURI(prefix);

accessNext.user.js

Googleの検索結果、各種ブログを始め、以下のようなページでお役立ち。 * The Haskell 98 Report: Introduction * 赤い馬 - ときにそのガムはふくれますか?

firefox 2, Safari(GreaseKit)で動作チェック済み。

追記:2008/04/11

nextリンクの優先度付けアルゴリズムを変更。 なるべくキーワードに近いリンクを選択するように。

参考:

Purl 実行環境!

投稿者 nanki 2008-02-23 21:47:00 GMT

shadow

:区切りの逆ポーランド記法言語、purlのランタイムを公開。

http://purl.netswitch.jp/purl/1:load:3:shadow:medium:to.png
上の画像のURLがそのままソースコードになっています。

こちらから画像をアップロードすると、統合開発環境に飛びます。

こんなかんじ

rubyruby

といったことが、URLだけで実現可能。


Kanasan.JS#2

投稿者 nanki 2007-12-11 00:34:00 GMT

朝九時から夜九時まで、prototype.jsを読む、という勉強会が開催されていたので、夕方の数時間だけオンラインで参加。 読んでいるソースのバージョンが違っていて、行数指定の話の内容を勘違いしていた。

僕が読んだ範囲は、prototype.js v1.6.0 l.587 - l.1193, Enumerable - Ajax.Baseまで。

Kanasan.JS本体の人はもっと広いはず。

Enumerable

l.587

Rubyと似たのが多いので、ほとんど違和感無く読める。 each の実装は_eachのみに依存して、他のメソッドはeachのみに依存している。

l.775

zipの実装がなかなか素敵。

こんな使い方。

>> [1, 2, 3].zip([1, 2, 3], [1, 2, 3]);
[[1, 1, 1], [2, 2, 2], [3, 3, 3]]

>> var sum = function(r,m) { return r + m };
>> [1, 2, 3].zip([1, 2, 3], [1, 2, 3], function(e){return e.inject(0, sum)})
[3, 6, 9]
// l.775
  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  }

[this].concat(args).map($A)と、collections.pluck(index) がキモ。

Array

l.813

WebKitでは、NodeList#toArrayにネイティブ実装を使う。 できれば、パフォーマンスのためのコードは、一目でわかるようにしておいて欲しいorおきたい。 パフォーマンスのためなのか、互換性のためなのかがわからない。(できればかかわりたくない)

l.837

this.length = 0;で配列の初期化ができるとは、知らなかった。 ちなみに、元の長さより長くすると、undefinedでpadされる。

l.877

array.uniq遅そう。 arrayがソート済みの場合、array.uniq(true)でずいぶん速くなりそう。 次のintersectもdetectで遅そうだけど、arrayはEnumerableであれば何でもよさそうなので、これでいいか。 気をつけて使う。 Enumerableって、極端な話、each実行するたびに結果が違ってもいいんだもんな。

Number

l.956

toColorPart:0-255の値を2桁16進にするためのコード。

l.979

Math.ceil とかをNumber#ceilにしてる。methodize().

Hash

l.987

いきなりのif文がわけわからないが、なにかの挙動の違いで、パフォーマンスをあげようとしているのだろうか。 v1.6.0.1ではなくなっているので、あまり気にしないで読み進む。

eachの中で、pair.key, pair[0] の両方のアクセス方法を許すようなコードが書かれている。

l.1016

この書き方(無名関数の中で関数を作って、return でオブジェクトを返す)は、JavaScriptでプライベート関数を実現するイディオムのようなもので、 この場合は、toQueryPairは、外からはアクセスできない。

1.5時代のソースも見てみると、Hashががらりと変わっていることに気がつく。 1.5では、値をthis自身に、1.6では、値をthis._objectに格納している。

// 1.5
$H({a: 3})["a"] // => 3

// 1.6
$H({a: 3})["a"] // => undefined
$H({a: 3}).get("a") // => 3
$H({a: 3}).toObject()["a"] // => 3

うーん、これは結構使ってしまっている気がするんだなぁ。

l.1072

toQueryString 突然のcurryに緊張が走る。 配列をQueryStringに変換する際、toQueryPairの第一引数を固定しているだけのようだ。

>> $H({a: [1,2,3]}).toQueryString()
"a=1&a=2&a=3"

そういえば、最近、近所の交差点がcurry激戦区になりつつある。

l.1100

toTemplateReplacements. どうも、1.5, 1.6間でHashの実装が大きく変わったのは、これが原因のような気もする、この関数。 evaluateの中で勝手に呼ばれるので、気にする事はない。

//v1.5
>> (new Template("hello #{name} #{each}.")).evaluate($H({name: 'ujihisa'}))
"hello ujihisa function (iterator) {
....
...
}."

//v1.6
>> (new Template("hello #{name} #{each}.")).evaluate($H({name: 'ujihisa'}))
"hello ujihisa ."

ObjectRange

RubyのRangeみたいなの。 Range だと、DOMのRangeとかぶるので、ObjectRangeらしい。 exclusiveは、最後の要素を含まないか含むか。

start, endは、比較可能で、succが実装されていれば何でもよい。 比較も、オーバライド可能なメソッドにしてほしいな。

Arrayは比較可能なので、Array.prototype.succを実装すると、ObjectRangeで使えるようになると思う。 あんまり意味なさそうだけど。

$R("a", "c").map()
// => ["a", "b", "c"]

Array.prototype.succ = function() {
  var clone = this.clone();
  clone.push(this.last());
  return clone;
}

$R([1], [1,1,1]).map()
// => [[1], [1, 1], [1, 1, 1]]

Ajax.Responders

Ajaxのリクエストに関するイベントリスナを管理している。

l.1162

なぜapplyの第一引数がresponderなのか、という話題になる。 たぶん、こういう使い方をしたいから、という気がする。

Ajax.Responders.register({onComplete: function () { Ajax.Responders.unregister(this)}});

Ajax.Base

あんまり読みどころがない。 目がAjax.Requestの中の$superを見つけてしまう。なんだこれ。

$super

  initialize: function($super, url, options) {
    $super(options);

という使われ方から、親クラスのメソッドを呼び出す関数だというのはわかるのだが・・・

$superで検索すると、文字列として見つかる。あやしい。

  1. value には関数が入る。
  2. value.argumentNames()で関数宣言のソース文字列を正規表現でパースし、引数のリストを取得して、
  3. 第一引数が$superならば、その関数を置き換えている。
  4. function() { return ancestor[m].apply(this, arguments) }; が$superの中身。
  5. wrapで、method(arg1, arg2) という呼び出しを、method($super, arg1, arg2) に置き換えつつ、
  6. 置き換えた事を悟られないように、toString, valueOfをオーバーライドしている。

嘘を嘘で塗り固めたような・・・。

//l.76
Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value, value = Object.extend((function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method), {
          valueOf:  function() { return method },
          toString: function() { return method.toString() }
        });
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

本筋とは関係ないけど、if (!Object.keys({ toString: true }).length) というコードを見ると、一瞬困惑するRuby脳。

おまけ

l.1222

      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';

この謎の&_=、Safariのどれかのバージョンで、POSTのリクエストの後ろに変な文字列をつけるバグへの対策らしい。&_=........となるので、切り捨てられる。

参考:

OSXのユーザ辞書の作り方

投稿者 nanki 2007-11-26 04:20:00 GMT

Leopardからは、小学館の辞書データとWikipediaが追加されて、とっても便利なDictionary.app.

たぶん、キーボード使用を前提とした小気味よいインターフェースがいいんだと思う。 慣れてしまうと、もう生のWikipediaを使う気はなかなか起きない。

こうなると、なんでもかんでも、同じインターフェースで済ませたくなるもので、そんな時に、こんな検索しにくいページを見たら、「Dictionary.appで検索できて然るべき」と思ってしまうのが自然な流れ。

というわけで、ライフゲームの辞書、Life LexiconをDictionary.app用に変換します。

まずは、テンプレートをコピーしてくる。 試しにmakeすると、あっさり辞書が作られる。 make installすれば、~/Library/Dictionaries 以下にコピーされ、Dictionary.appから検索できる状態になる。(Dictionary.appの環境設定で有効にしないとだめかも)

$ cp -r /Developer/Examples/Dictionary Development Kit/project_templates dict
$ cd dict
$ make
"""/Developer/Extras/Dictionary Development Kit"/bin"/build_dict.sh"  "My Dictionary" MyDictionary.xml MyDictionary.css MyInfo.plist
- Building My Dictionary.dictionary.
- Cleaning objects directory.
- Preparing dictionary template.
- Preprocessing dictionary sources.
- Extracting index data.
- Preparing dictionary bundle.
- Adding body data.
- Preparing index data.
- Building key_text index.
- Building reference index.
- Fixing dictionary property.
- Copying CSS.
- Copying other resources.
- Finished building ./objects/My Dictionary.dictionary.
echo "Done."
Done.

重要なのは、MyDictionary.xmlファイル。 辞書の元となるデータをパースして、XMLファイルを作ってやる。

<?xml version="1.0" encoding="UTF-8"?>                                                                               
<d:dictionary xmlns="http://www.w3.org/1999/xhtml" xmlns:d="http://www.apple.com/DTDs/DictionaryService-1.0.rng">    
  <d:entry id="blinker puffer" d:title="blinker puffer">                                                             
    <d:index d:value="blinker puffer" d:title="blinker puffer" />                                                    
    <d:index d:value="puffer" d:title="blinker puffer" />                                                            
    <h1><span class="headword">blinker puffer</span></h1>                                                            
    <span class="meaning">Any <a href="x-dictionary:r:puffer">puffer</a> whose output is <a href="x-dictionary:r:blinker">blinker</a>s.  However, the term is particularly used for p8 c/2 puffers.  ....
    </span>
  </d:entry>
  <!-- 以下d:entry の山 -->
</d:dictionary>

意味はほとんど見た目通り。
それぞれの項目は、インデックスと内容のXHTMLで構成される。 他の単語を参照する、href=”x-dictionary:r:ID”とかが特徴的か。 上の例では「blinker puffer」という単語に対して、pufferでも検索に引っかかって欲しいので、indexに加えてある。

パースしてXMLを吐くコードは、汎用性がないのでここには載せないが、今回は以下のようなLifeを表す図を、rcairoでpng画像に変換もしている。

OO..
O..O
..OO

画像などのリソースは、dict/OtherResources 以下の適当な場所に置いておき、普通のHTMLのように、<img>タグを使って埋め込む。

<p><img src="Images/blinker%20puffer_0.png"/></p>

これで、

$ make
$ make install

すれば、ユーザ辞書の完成。

続き

さて、これだけで終わりではもったいないので、先日作ったQuartz Compositionを組み込めないか、頑張ってみる。

HTMLにQuartz Compositionを組み込むのは簡単で、EMBEDタグを使って、

<embed id="composition" type="application/x-quartzcomposer" src="LifeLexicon.qtz" width="300px" height="300px" />

とする。

これで、普通のムービーならスタートするのだけど、ライフゲームなので第0世代の画像を渡してやらないといけない。

Quartz ComposerのWebKitプラグインにはJavaScript APIが用意されていて、publishしたパラメータを設定できるようになっている。 が、文字列しか設定できないようだ。画像はどうやって渡すのだろう。

調べてみると、Compositionの中で、Image Downloaderというパッチを使うらしい。 Image Downloaderは指定されたURLの画像をImageとして出力するパッチ。 なるほど。 ImageDownloaderの入力を、imageLocationという名前でPublishして・・・

var composition = document.getElementById("composition");
composition.setInputValue("imageLocation", "fullpath.png");

こんな感じになるのかな。 しかし、フルパス指定じゃないといけないのが面倒だなぁ・・・

先生、こちらにできあがったものが用意してあります。

結局、location.href と、EMBEDに勝手に付け加えたimage属性から、フルパスを取得する仕組みに。 文中のSpaceはCompositionというか、Universeというか。

//LifeLexicon.js
(function () {
  var waitForSpaces = function (spaces) {
    if (spaces.length == 0) return;

    if (spaces[0].loaded && spaces[0].loaded()) {
      var space = spaces.shift();
      var base = location.href.substr(0, location.href.lastIndexOf("/"))
      space.setInputValue("imageLocation", base + space.getAttribute('image'));
    } 
      
    with ({callee: arguments.callee}) {
      setTimeout(function () {callee(spaces)}, 100);
    }
  };

  var arrayFromNodeList = function (nodelist) {
    var result = [];
    for (var i = 0; i < nodelist.length; i++) {
      result.push(nodelist.item(i));
    }
    return result;
  };

  waitForSpaces(arrayFromNodeList(document.getElementsByTagName('EMBED'))); 
})();

なぜか、<script src="LifeLexicon.js">だと読み込まれないので、

<script>
  var tag = document.createElement("SCRIPT");
  tag.src = "./LifeLexicon.js";
  document.getElementsByTagName("HEAD")[0].appendChild(tag);
</script>

というコードも、<d:entry>内に埋め込むように。

これで「生きた」LifeLexiconが完成。

LifeLexicon

CREDITをどこかに入れないと配布できないらしいので、欲しい人には直接送ります。 いないだろうけど。

誰か、Ruby Reference Manualをこれで。

参考: