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


<div class="entry">
  <h2><a href="" /></h2>
</div>

このような文書で、aのリンク先を訪れると、div.entryの色が変わるようにしたい。

XPathみたいに、こういう子要素を持つノード、という指定は、CSSではできないっぽいので、JavaScriptでやるのが穏便かと思いきや、

div.entry {
  position: relative;
}
 
div.entry > h2 > a:visited:after {
  content: " ";
  display: block;
  position: absolute;
  top: 0px;
  left: 0px;
  right: 0px;
  bottom: 0px;
  z-index: -1;
  background-color:gray;
}

でうまくいった。

iPhone向けなので、Safari限定で考えられるのがうれしいところ。

実際には背景色ではなくて、新たに生成されたノードをdiv.entryにぴったり張り付くようにしている。

で、この情報をJavaScriptから取れるとどのサイトにアクセスしたか、という情報が取れてしまうので色々対策されているらしいんですが… どうやって防ぐんだろう。


uuuu & Gauche本読書会 & SICP読書会 & 異業種交流会

投稿者 nanki 2008-04-28 12:25:00 GMT

uuuu

大阪でミーティングがあった後、物件を見に行って、その後帝人ビルの食堂みたいなところでしばらくお茶(というかミックスジュース)して、北浜まで歩いて帰るつもりだったけど、もう午後も遅い時間、uuuu始まるの17時じゃなかったか、と思って、突如参加することに決めた。

uuuuとは、(当面)みんなでMITの講義ビデオを見る会。

会場に着いてから無線につながったので開始時間を見ると19時から…orz 19時まで、JavaScriptのコードをいじったりしながら、早めに来たujihisaと合流.

今回は第一回目なので、コースの説明や試験の説明などもある。 スピーカのセッティングなどをしつつ、そこは聞き流す。

なぜアルゴリズムを学ぶのか?パフォーマンス? パフォーマンスより大事なことはたくさんある。正確さ、単純さ、メンテナンス性、ユーザビリティ…etc パフォーマンスはお金のようなもので、これらを手に入れるにはパフォーマンスをお金のように支払う。 それに、速いと楽しいじゃん!

みたいな話の後、insertion sortとmerge sortについて詳細に分析する。

Gauche本読書会

40分ほど遅刻。会場の入口でもう一人の遅刻者と合流。

昨日突然参加を決めたのでGauche本は持ってきていない。 隣の人に見せてもらうメソッドでyukkyに見せてもらう。 LISP, Schemeの歴史やemacsの話を読む。 よし、今日はemacsしか使わないぞ、と思い立ったのも束の間、.emacsの編集にvimを使ってるのに気がついてあきらめた。

リテラルをいくつか、goshで試してお昼。

SICP読書会

問題 1.5くらいまで。

問題1.3, a b c のうち大きい方二つの二乗和を計算する。

(define (sum-of-squares a b) (+ (* a a) (* b b)))
(define (f1.3 a b c)
  (cond ((and (< a b) (< a c)) (sum-of-squares b c))
        ((and (< b a) (< b c)) (sum-of-squares a c))
        ((and (< c a) (< c b)) (sum-of-squares a b))))

こんな長いのは勘弁してほしいので余った時間でminigolf.

(define (f1.3 a b c)
  (if (> a b)
      (if (> b c)
          (+ (* a a) (* b b))
          (f1.3 a c b))
      (f1.3 b a c)))

おお、再帰脳。 condもなくなったので、BiwaSchemeでも動く。

yharaさんのLT、いいLISPのコードは、見た目がこう!という話を聞いて書き直す。(indentが単調増加してるのがいいらしい)

(define (f1.3 a b c)
  (if (< a b)
      (f1.3 b a c)
      (if (< b c)
          (f1.3 a c b)
          (+ (* a a) (* b b)))))

実は最後のコード以外には致命的なバグがある。

こんな短いコードにバグを仕込めるなんて… このコードにバグはないか?という視点で見ると、すぐに気がつくんだけど。

こんなコードでテストする。このテストにパスしなかったら、問題解けてないのと同じなのだから、テスト重要。

(map (lambda (x) (apply f1.3 x)) 
          '((1 2 14) (1 14 2) (2 1 14) (2 14 1) (14 1 2) (14 2 1) (14 2 2)
            (2 14 2) (2 2 14) (10 10 2) (10 2 10) (2 10 10) (10 10 10)))

これで、大小関係においては網羅しているかなぁ。 テストデータが実装を反映した(比較演算子を使っているという)ものになっているのが、気にかかるがどうすべきなんだろう。

ujihisaのLTで、少し群の話がでてくる。 (演算子)とやると、単位元がでてくるはず。

gosh> (+)
0
gosh> (*)
1
gosh> (and)
#t
gosh> (or)
#f
gosh> (list)
()

naoya_tさんのLT、Scheme on PIC発表資料かっこよすぎる。

異業種交流会

SICP読書会でその存在を知り、突然行く事にしたのだが、知らない人たくさん、知ってる人もたくさんで楽しかった。 知らないはずなのに、どこかでつながっていたり、実はとてもご近所さんだったり、どっかですれ違っていたり。 ごはんもおいしかったし。

帰りにお茶してタクシーで帰った。ぐったり。

百万ベンチャーw

1000000.times {puts "チャー"}

QRコードをPostScriptで吐き出す、という話。

QRコード自体の生成には、libqrencode, PostScript 生成には、cairo を利用している。

友人曰く、IllustratorでQRコードをパスとして扱うには、数万円するプラグインを買うか(結構高機能)、ラスター画像をトレースするのが一般的なんだとか。 えぇぇ。

そんなこんなで作りました。betaだよ。

QRコードジェネレータ(PostScript版)

参考:

次のページへ - 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だけで実現可能。


プログラミング言語 purl を ruby で実装

投稿者 nanki 2008-02-21 01:40:00 GMT

言語処理系を実装するのは、初めて(だと思う)

PostScript などと同じ逆ポーランド風の言語で、画像処理用途。

以下purlのソース。

2:load:dup:2:shadow:swap:composite:geom:1:load:100:100:4:pull:4:pull:crop:swap:composite:to.png

:がリテラルの区切り、その他は数字と演算子。 上のコードでは、2番の画像をロードして、ドロップシャドウしたものに1番の画像を背景として合成している。

ソースコード中に改行は入れられない。何故ならコードはURLの一部だから…

sample


DoCoMoのラジオボタン

投稿者 nanki 2008-02-20 11:40:00 GMT

DoCoMo機から

<input type='radio' checked='checked' name='id' value=''/>

を送信すると、id=’on’ が送られてくるようだ。

.blank? を使って判定してると危険。

詳細情報求ム。


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のリクエストの後ろに変な文字列をつけるバグへの対策らしい。&_=........となるので、切り捨てられる。

参考:

Railsアプリケーションの国際化 - Globalize

投稿者 nanki 2007-08-26 10:15:00 GMT

Globalizeを使ってみる。

セットアップ

適当なRailsアプリを作って、Globalizeをインストール。

$ script/plugin install http://svn.globalize-rails.org/svn/globalize/trunk
....
$ mv vendor/plugins/trunk vendor/plugins/globalize
$ rake globalize:setup

CSV::Cellがinspectされたようなエラーが出るので、vendor/plugins/globalize/tasks/data.rake を修正する。

--- ./vendor/plugins/globalize/tasks/data.rake.orig     0000-00-00 00:00:00.000000000 +0000
+++ ./vendor/plugins/globalize/tasks/data.rake  0000-00-00 00:00:00.000000000 +0000
@@ -17,3 +17,3 @@

-    columns = reader.shift.map { |column_name| cnx.quote_column_name(column_name) }
+    columns = reader.shift.map { |column_name| cnx.quote_column_name(column_name.data) }
     column_clause = columns.join(', ')
@@ -24,3 +24,3 @@
       raise "No header defined"     unless column_clause
-      values_clause = row.map { |v| cnx.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(', ')
+      values_clause = row.map { |v| cnx.quote(v.data).gsub('\\n', "\n").gsub('\\r', "\r") }.join(', ')
       sql = "INSERT INTO #{table_name} (#{column_clause}) VALUES (#{values_clause})"

さらに、vendor/plugins/globalize/data/language_data.csv の空行がエラーを起こしているので修正。

$ rake globalize:setup
...

成功。

View翻訳

base_languageを英語に。

# config/environment.rb
Globalize::Locale.set_base_language("en-US")
$ script/console
>> "March [month]".t
=> "March"
>> Globalize::Locale.set("ja-JP")
=> #<Globalize::Locale...
>> "March [month]".t
=> "3月"
>> Time.now.loc "%A"
=> "日曜日"

ふむふむ。この辺は、gettextの方が使いやすそうかなぁ。

Model翻訳

適当なモデルを作成

$ script/generate model Product
...
# db/migrate/001_create_products.rb
  def self.up
    create_table :products do |t|
      t.column :name, :string
      t.column :manufacturer, :string
    end
  end
$ rake db:migrate
$ script/console
>> p = Product.create :name => "Pucchin Pudding", :manufacturer => "Glico"
=> #<Product:...
>> p.name
=> "Pucchin Pudding"
>> p.manufacturer
=> "Glico"

# jaで保存。
>> Globalize::Locale.set("ja-JP")
>> p.name = "プッチンプリン"
>> p.manufacturer = "グリコ"
>> p.save
=> true

# jaで読む。
>> p.reload
>> p.name
=> "プッチンプリン"
>> p.manufacturer
=> "グリコ"

# enで読む。
>> Globalize::Locale.set("en-US")
>> p.reload
>> p.name
=> "Pucchin Pudding"
>> p.manufacturer
=> "Glico"

ちゃんと動いてる。

この時、productsテーブルにはbase_languageで格納され、それとは別にglobalize_translationsテーブルに、翻訳情報を保持している。

sqlite> SELECT * FROM globalize_translations WHERE id > 7088;
id          type              tr_key      table_name  item_id     facet       built_in    language_id  pluralization_index  text                   namespace
----------  ----------------  ----------  ----------  ----------  ----------  ----------  -----------  -------------------  ---------------------  ----------
7089        ModelTranslation              products    18          name        t           2723                              プッチンプリン
7090        ModelTranslation              products    18          manufactur  t           2723                              グリコ

Object#_が定義されていたので、gettextとケンカするかと思ったけど、一緒に動いた。 偶然かもしれないけど。

screenshot

そんなこんなで、↑こんなアプリケーションができてしまう。
はたして、使う機会は訪れるのか?

追記:

  • eager loadingを行う時は、:include_translatedを使う。
  • が、対応が中途半端で、user.product_nameとアクセスしないとだめっぽい。
class User
  belongs_to :product
end

user = User.find(:first, :include_translated => :product)

user.product.name #=> NG
user.product_name #=> OK
参考: