■
(ε・◇・)っ ひっこした http://uupaa.hatenablog.com/
(ε・◇・)з そのうちここは綺麗サッパリ消えます
New Features in Android Browser 4.0
via http://www.mobilexweb.com/blog/android-4-0-browser-html5
- Android Browser 4.0 は将来的に Chrome に置換されるけど、まだ時間が必要。4.0 に搭載されているブラウザは従来の改良版
- Google Chrome と Android Browser のブックマーク同期
- レンダリング速度が向上
- WebKit Core と V8 Crankshaft を更新し、JavaScript ベンチマークスコアが5.5速倍に
- New Features
- SVG
- Motion Sensor API
- window.addEventListener("deviceorientation", ...)
- CSS trasnform 3D
- E>F { transform: matrix3d(...) }
- XMLHttpRequest Lv2 and Cross-Origin Resource Sharing
- File API
- new FileReader()
- Media Capture API
- navigator.device.capture.captureImage(...)
- Typed Binary Arrays
- int32Array, ...
- window.PerformanceTiming
- console.memory.*
- Not Supported Features
- No Server-sent events
- No WebSockets
- No IndexedDB
- No Web Notifications
- No WebGL
- No History Management API
- No input type="color", "range", "date", ...
ブックマークの同期機能は iPhone Safari にも欲しい機能です。
手早くブックマークしておいて、あとからタブレットやPCで見るといった使い方ができますね。
Android Browser が SVG をサポート
これでブラウザ側のサポートはひと通り揃ったのですが。ツールや作業者のこなれ具合を鑑みると、
- フリーのオーサリングツールが普及していない。SVG は HTML や CSS のようにテキストエディタでハンドリングしきれるものではない
- デザイナーとプログラマーの繋ぎこみノウハウが蓄積していない
- SVG の互換性情報の共有もまだまだ
- SVG の仕様が巨大で、ついつい欲張ると物ができない
- IE が、ここでも足をひっぱる
- SVG DOM をサポートしているライブラリが無い
といった弱みがあります。これらをクリアしないと SVG ブームは来ないな〜 という感じしてます。
SVG DOM をサポートするライブラリが登場すれば、 SVG で UI を構築する試みが始まり、そのへんから普及が始まるんじゃないかな〜 とも思いますが。
# uupaa.js 0.8 に仕込んでおいた SVG DOM ビルダー機能も、やっと陽の目を見るかな〜 と
mm.mfx 群舞向きアニメーションの実装
uupaa.js 0.8 で書いた uu.fx は1つのオブジェクトを高速で滑らかにアニメーションさせる能力に特化してまして、
沢山のオブジェクトが整列してパラパラと切り替わるタイプのアニメーションには使いづらいものでした。
ずっと宿題になってましたが、requestAnimationFrame を利用し、mm.mfxとして実装してみました。
# mfx は Mass Effectの略です
デモ: http://mofmof-js.googlecode.com/svn/trunk/test/Math.easing/tiling.htm
ブラウザ専用にコードを書いてないので、このままだとちょっと滑らかさが足りないのですが、ブラウザに特化した版ものちほど実装する予定です。
(ε・◇・)з CoffeeScript と mofmof.js
(ε・◇・)з CoffeeScript は Ruby や Python っぽく書けて JavaScript にコンパイルできるプリティな言語だとか!
(ε・◇・)з 噂では CoffeeScript で書くとコードが短くなるとか!!
物は試しに http://jashkenas.github.com/coffee-script/ にある幾つかのコード片と、mofmof.js 混じりで書いた場合のコードを比べてみました。
(ε・◇・)з mofmof.js はここだよ → http://code.google.com/p/mofmof-js/w/list
Syntax
// coffee # Assignment: number = 42 opposite = true # Conditions: number = -42 if opposite # Functions: square = (x) -> x * x # Arrays: list = [1, 2, 3, 4, 5] # Objects: math = root: Math.sqrt square: square cube: (x) -> x * square x # Splats: race = (winner, runners...) -> print winner, runners # Existence: alert "I knew it!" if elvis? # Array comprehensions: cubes = (math.cube num for num in list) // coffee -> js ---------------------------- var cubes, list, math, num, number, opposite, race, square; var __slice = Array.prototype.slice; number = 42; opposite = true; if (opposite) number = -42; square = function(x) { return x * x; }; list = [1, 2, 3, 4, 5]; math = { root: Math.sqrt, square: square, cube: function(x) { return x * square(x); } }; race = function() { var runners, winner; winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : []; return print(winner, runners); }; if (typeof elvis !== "undefined" && elvis !== null) alert("I knew it!"); cubes = (function() { var _i, _len, _results; _results = []; for (_i = 0, _len = list.length; _i < _len; _i++) { num = list[_i]; _results.push(math.cube(num)); } return _results; })(); // mofmof ---------------------------- // Assignment: var number = 42, opposite = true; // Conditions: opposite && (number = -42); // Functions: function square(x) { return x * x; } // Arrays: list = [1, 2, 3, 4, 5]; // Objects: math = { root: Math.sqrt, square: square, cube: function(x) { return x * square(x); } }; // Splats: function cube(winner, runners, /*... */) { return print.apply(null, arguments); } // Existence: elvis != null && alert("I knew it!"); // Array comprehensions: cubes = list.filter(math.cube);
(ε・◇・)з 基本 Syntax だと、あまり変わった感じしませんね〜
Functions
// coffee square = (x) -> x * x cube = (x) -> square(x) * x fill = (container, liquid = "coffee") -> "Filling the #{container} with #{liquid}..." // coffee -> js ---------------------------- var cube, square; square = function(x) { return x * x; }; cube = function(x) { return square(x) * x; }; var fill; fill = function(container, liquid) { if (liquid == null) liquid = "coffee"; return "Filling the " + container + " with " + liquid + "..."; }; // mofmof.js ---------------------------- function square(x) { return x * x; }; function cube(x) { return square(x) * x; } function fill(container, liquid /* = coffee */) { return "Filling the @@ with @@...".f(container, liquid || "coffee"); }
(ε・◇・)з Coffee は function が -> になってる分だけタイプ数少なめですね
Loops and Comprehensions
// coffee # Eat lunch. eat food for food in ['toast', 'cheese', 'wine'] // coffee -> js ---------------------------- var food, _i, _len, _ref; _ref = ['toast', 'cheese', 'wine']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { food = _ref[_i]; eat(food); } // mofmof ---------------------------- ['toast', 'cheese', 'wine'].forEach(eat); or var iter = Hash(['toast', 'cheese', 'wine']), food; while (food = iter.next()) { eat(food); }
(ε・◇・)з mofmof.js はforEachやイテレータ使ってます。実行速度が重要でなければ、このへんは好みかな〜
// coffee countdown = (num for num in [10..1]) // coffee -> js ---------------------------- var countdown, num; countdown = (function() { var _results; _results = []; for (num = 10; num >= 1; num--) { _results.push(num); } return _results; })(); // mofmof ---------------------------- countdown = 10..$(1);
(ε・◇・)з mofmof.js は Number.prototype.$ を拡張して 10..$(1) で 10 から1までの配列を生成していますよ〜
// coffee yearsOld = max: 10, ida: 9, tim: 11 ages = for child, age of yearsOld "#{child} is #{age}" // coffee -> js ---------------------------- var age, ages, child, yearsOld; yearsOld = { max: 10, ida: 9, tim: 11 }; ages = (function() { var _results; _results = []; for (child in yearsOld) { age = yearsOld[child]; _results.push("" + child + " is " + age); } return _results; })(); // mofmof ---------------------------- var yearsOld = { max: 10, ida: 9, tim: 11 }; Hash.map(yearsOld, function(age, child) { return "@@ is @@".f(child, age); });
(ε・◇・)з Coffee側のコードはシンプルですね〜
Array Slicing and Splicing with Ranges
// coffee numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] copy = numbers[0...numbers.length] middle = copy[3..6] // coffee -> js ---------------------------- var copy, middle, numbers; numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; copy = numbers.slice(0, numbers.length); middle = copy.slice(3, 7); // mofmof ---------------------------- var numbers = 0..$(9); var copy = numbers.clip(0, numbers.length); var middle = copy.clip(3, 6);
(ε・◇・)з もふもふ!
// coffee numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] numbers[3..6] = [-3, -4, -5, -6] // coffee -> js ---------------------------- var numbers, _ref; numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; [].splice.apply(numbers, [3, 4].concat(_ref = [-3, -4, -5, -6])), _ref; // mofmof ---------------------------- numbers = 0..$(9); numbers.swap(3, (-3).$(-6));
(ε・◇・)з mofmof 側は、Array#swap で配列の一部をごっそり入れ替えてます〜
Everything is an Expression (at least, as much as possible)
// coffee globals = (name for name of window)[0...10] // coffee -> js ---------------------------- var globals, name; globals = ((function() { var _results; _results = []; for (name in window) { _results.push(name); } return _results; })()).slice(0, 10); // 全部とってきて10個だけ返す // mofmof ---------------------------- globals = Hash.keys(window, 10); // 10個とってきて返す
(ε・◇・)з mofmof の Hash.keys は、第二引数で列挙する要素の最大数を指定できるので、10個だけ列挙して返します〜
Classes, Inheritance, and Super
// coffee class Animal constructor: (@name) -> move: (meters) -> alert @name + " moved #{meters}m." class Snake extends Animal move: -> alert "Slithering..." super 5 class Horse extends Animal move: -> alert "Galloping..." super 45 sam = new Snake "Sammy the Python" tom = new Horse "Tommy the Palomino" sam.move() tom.move() // coffee -> js ---------------------------- var Animal, Horse, Snake, sam, tom; var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; Animal = (function() { function Animal(name) { this.name = name; } Animal.prototype.move = function(meters) { return alert(this.name + (" moved " + meters + "m.")); }; return Animal; })(); Snake = (function() { __extends(Snake, Animal); function Snake() { Snake.__super__.constructor.apply(this, arguments); } Snake.prototype.move = function() { alert("Slithering..."); return Snake.__super__.move.call(this, 5); }; return Snake; })(); Horse = (function() { __extends(Horse, Animal); function Horse() { Horse.__super__.constructor.apply(this, arguments); } Horse.prototype.move = function() { alert("Galloping..."); return Horse.__super__.move.call(this, 45); }; return Horse; })(); sam = new Snake("Sammy the Python"); tom = new Horse("Tommy the Palomino"); sam.move(); tom.move(); // mofmof ---------------------------- mm.Class("Animal", { init: function(name) { this.name = name; }, move: function(meters) { alert("@@ moved @@m.".f(this.name, meters)); } }); mm.Class("Snake:Animal", { move: function() { alert("Slithering..."); this.superCall("move", 5); } }); mm.Class("Horse:Animal", { move: function() { alert("Galloping..."); this.superCall("move", 45); } }); var sam = mm("Snake", "Sammy the Python"); var tom = mm("Horse", "Tommy the Palomino"); sam.move(); tom.move();
(ε・◇・)з mofmof にもクラスあるんだよ〜
(ε・◇・)з 最大継承数は1段に限定してある(昔は3段までサポートしてた)けど、1段継承できればニーズの9割カバーできるからOKなんだよ〜 (それ以上継承したい人は変態的なコードを書きたい人だけだと思うよ〜
Destructuring Assignment
// coffee theBait = 1000 theSwitch = 0 [theBait, theSwitch] = [theSwitch, theBait] // coffee -> js ---------------------------- var theBait, theSwitch, _ref; theBait = 1000; theSwitch = 0; _ref = [theSwitch, theBait], theBait = _ref[0], theSwitch = _ref[1]; // mofmof ---------------------------- var theBait = 1000, theSwitch = 0; [theBait, theSwitch].swap(0, [theSwitch, theBait]);
(ε・◇・)з ここでも Array#swap が出てきてます〜
Chained Comparisons
// coffee cholesterol = 127 healthy = 200 > cholesterol > 60 // coffee -> js ---------------------------- var cholesterol, healthy; cholesterol = 127; healthy = (200 > cholesterol && cholesterol > 60); // mofmof ---------------------------- var cholesterol = 127 healthy = cholesterol.inRange(200, 60);
(ε・◇・)з Coffee のほうが分かりやすいね〜
おしまい
(ε・◇・)з mofmof.js も結構イケてるんじゃないでしょうか!
Screen Transition Traversal Pattern
とある開発環境で iPhone/Android アプリを書いてます。開発言語は js ですが Titanium ではありません。
今回の開発で実現したい事の1つに「可能なら UI を自動でテストしたい」というのがあります。
ぼーっとしていたら、以下のようなロジック(パターン)を ピコーン しました (恐らくボクが知らないだけで車輪の再発明なんだろうけどさ!)
一言でいうと、画面構造をREST風味にステートレス化するということです。途中の画面の状態を再現するパラメタをパスに埋め込む事ができ、再現する仕組みです。
このような構造を取ることにより、
- 画面A が 画面B を知らず、画面B が 画面A を知らなくても成立する(かもしれない)
- 開発終盤の仕様変更と再テストに強くなる。途中にどのような画面が新たに挟まっても手直しが少ない(かもしれない)
- 画面A から 画面B に遷移する UI (ボタン等)には、最終的に /A/B といったパスが設定されるが、このパスは画面A が固定で持たず、UI 全体を統括するマネージャ的存在が 画面A に知らせる。
といった良いことがありそうです。
こんな感じ?
たたき台を考えてみました。mofmof.js ベースです。
閉じる/開く画面を決定するロジックのキモは、 mm.Class.Screen#resolve に実装する形になります(まだ空っぽです)
mm.Class.singleton("Screen", { // シングルトンクラスの定義。 mm("Screen").pwd() のように使用する _current: "/", // カレントパス pwd: function() { // @return String: カレントパスを返す。pwd で通じるよね? return this._current; }, cd: function(path) { // @param String: 移動先のパスを指定する var r = this.resolve(this._current, path); // 閉じる/開く画面の解決 if (r.close.length) { // r.close の順に画面を破棄する } if (r.open.length) { // r.open の順に画面を破棄する } this._current = path; // カレントパスを更新 }, resolve: function(before, // @param String: after) { // @param String: // @return Hash: { close, open, base } // close - StringArray: // open - StringArray: // base - String: base dir to open if (before === after) { return { close: [], open: [], base: "" }; } var rv = { close: [], open: [], before: base: "" }, ... // closeする画面と、openする画面の配列 をここで作成する return rv; } });
予想では、resolve メソッドの結果は以下のようになるはずです。
mm("Screen").resolve("/A/B/C", "/A/B/C") -> { close: [], open: [], before: "/A/B/C", after: "/A/B/C" } mm("Screen").resolve("/A/B/C", "/A/B") -> { close: ["C"], open: [], before: "/A/B/C", after: "/A/B" } mm("Screen").resolve("/A/B/C", "/A/D") -> { close: ["C", "B"], open: ["D"], before: "/A/B/C", after: "/A/D" } mm("Screen").resolve("/A/B/C", "/") -> { close: ["C", "B", "A"], open: [], before: "/A/B/C", after: "/" } mm("Screen").resolve("/A/B/C", "/E/F") -> { close: ["C", "B", "A"], open: ["E", "F"], before: "/A/B/C", after: "/E/F" }
現在実装中なのですが、resolve が中々に手強いです。
実装できたらこのエントリを更新します。
追記
散々悩んで書いた汚いロジックがこちら。
「画面遷移どうすべ〜」→ ピコーン待ち → ピコーンきたー → ブログカキカキ → コードカキカキ 含めて8時間ぐらい。
resolve: function(before, // @param String: after) { // @param String: // @return Hash: { close, open, base } // close - StringArray: // open - StringArray: // base - String: if (before === after) { // match all -> nop return { close: [], open: [], base: "" }; } var close = [], open = [], base = "/", bary, aary, last, i, iz; bary = before.split("/"); // "/A/B/C" -> ["", A, B, C] aary = after.split("/"); // "/A/B" -> ["", A, B] // close while ((last = bary.pop())) { close.push(last); aary.length = bary.length; if (bary.join("/") === aary.join("/")) { // match break; } } bary = before.split("/"); // "/A/B/C" -> ["", A, B, C] aary = after.split("/"); // "/A/B" -> ["", A, B] if (after === "/") { // case: resolve("/A/B/C", "/") ; } else if (bary[1] !== aary[1]) { // case: resolve("/A/B/C", "/E/F") // ルート直下から違う場合は全てopen対象となる base = "/"; open = aary; open.shift(); } else { // open ,, close とは逆方向に走査を行い不一致要素を open に追加する // ,, 画面を開く際の起点となる要素をopenParentに設定する for (i = 1, iz = Math.max(bary.length, aary.length); i < iz; ++i) { if (aary[i] === void 0) { // outof index break; } if (base) { // 親画面が異なるため、親画面以下の全ての画面を追加する if (bary[i] !== aary[i]) { for (iz = aary.length; i < iz; ++i) { open.push(aary[i]); console.log("add @@".f(aary[i])); } break; } } else { base = bary[i]; } } } return { close: close, open: open, base }; }
(↑)のロジックは見て分かるように、最悪のコードです。日本語コメントが必要なくらいに最悪です。自分で書いてて「これはメンテできないわー」ってなりました。さらに幾つかのテストケースをパスできず「あかん、これはあかん…」とクソ悩んでました。
困ってしまって、2つ隣に座ってる同僚(セト神様)をティディベアに見立て、一人ティディベアデバッグ(夜中の4:00に一方的にブツブツ話かけたった)をした所、再度ピコーンが!!
セト神の啓示をうけ、5分でリライトしたロジックがこちら
resolve: function(before, // @param String: after) { // @param String: // @return Hash: { close, open, base } // close - StringArray: // open - StringArray: // base - String: base dir to open var close = [], open = [], ary1, ary2, base = "", i = 0, j = 0; if (before !== after) { ary1 = before.split("/"); // "/A/B/C" -> ["", A, B, C] ary2 = after.split("/"); // "/A/B" -> ["", A, B] while (ary1[i] === ary2[i]) { base = ary1[i++] || "/"; } j = i; while (ary1[i]) { close.push(ary1[i++]); } while (ary2[j]) { open.push(ary2[j++]); } } return { close: close.reverse(), open: open, base: base }; }
テストコード
(function() { 'mm("Screen").resolve("/", "/")'.test('{ close: [], open: [], base: "" }'); 'mm("Screen").resolve("/A", "/Z")'.test('{ close: ["A"], open: ["Z"], base: "/" }'); 'mm("Screen").resolve("/", "/A/B/C")'.test('{ close: [], open: ["A","B","C"], base: "/" }'); 'mm("Screen").resolve("/A", "/A/B/C")'.test('{ close: [], open: ["B","C"], base: "A" }'); 'mm("Screen").resolve("/A/B/C", "/A/B/C")'.test('{ close: [], open: [], base: "" }'); 'mm("Screen").resolve("/A/B/C", "/A/B")'.test( '{ close: ["C"], open: [], base: "B" }'); 'mm("Screen").resolve("/A/B/C", "/A/D")'.test( '{ close: ["C", "B"], open: ["D"], base: "A" }'); 'mm("Screen").resolve("/A/B/C", "/")'.test( '{ close: ["C", "B", "A"], open: [], base: "/" }'); 'mm("Screen").resolve("/A/B/C", "/E/F")'.test( '{ close: ["C", "B", "A"], open: ["E", "F"], base: "/" }'); 'mm("Screen").resolve("/A/B/C/D/E", "/A/Z/C/D/E")'.test('{ close: ["E", "D", "C", "B"], open: ["Z", "C", "D", "E"], base: "A" }'); "run".test("core.js - mm.Class.Screen"); })();
何が言いたいかというと、ティディベアデバッグ☆オススメ☆デス!!!!
IE10 で attachEvent が廃止された場合に備えましょう
Windows 8 には IE10 が標準搭載されます。これらは早ければ2012年の秋ごろまでにリリースされる予定です。
IE10 ではレガシーな(古い)機能やメソッドが切り捨てられると予告されています → 非推奨の DOM イベント
IE10 で attachEvent が廃止されると attachEventで IE かどうかを判別する(↓)のコードなどが魔女化するでしょう(バグになるでしょう)。
// オレオレライブラリによく見かけるコード片 var isIE = (window.attachEvent && !window.opera); この場合は ↓↓ これで良さげ var isIE = !!document.uniqueID;
アドネットワークからの広告を埋め込んでいるサイトでは、同様のコードが広告生成用の JavaScript の中にも埋まっているかもしれません。そちらに対する備えも必要ですね。
NO TEST, NO LIFE. NO DOC, NO LIFE
uupaa.js や mofmof.js には {@hoge 〜 }@hoge のようなコードブロックを切り落として Minify する機能があるので、「ソースコードにテストもドキュメントも全部埋め込むことが可能だな〜」って3年程前から考えてました。
そこで、Function.prototype.spec というメソッドを追加し、これにスペックを書き貯めたらどうだろうか(?)とか考えました。
たとえば
Array.range(1,7) で [1..7] 的な連続した数値の配列を生成する Array.range 関数があったとすると
// Array.range - range generator function Array_range(begin, // @param Number: begin end, // @param Number: end filter) { // @param Function/Number(= 1): filter or skip count // @return Array: [Number, ...] // @raise: Error("BAD_ARG") // @see: Array#range var rv = [], ri = 0, i = begin, iz = end, skip = 1; if (Type.isFunction(filter)) { for (; i <= iz; ++i) { if (filter(i) === true) { rv[ri++] = i; } } return rv; } if (Type.isNumber(filter)) { skip = filter; } if (skip <= 0) { throw new Error("BAD_ARG"); } for (; i <= iz; i += skip) { rv[ri++] = i; } return rv; }
この場合、スペックは以下のような感じで記述してます。
//{@spec Array.range.spec({ desc: ["Function", "range generator"], begin: ["Number", "begin"], end: ["Number", "end"], filter: ["Function/Number(= 1)","filter or skip count"], ret: ["Array", "[Number, ...]"], raise: 'Error("BAD_ARG")', see: "Array#range" }); //}@spec
まだ荒削りだけど、
- Function.prototype.spec._spec にスペック一覧が保存されている
- Function.prototype.spec._src に関数を実行可能な形(改行やコメントも含んだ文字列)で格納している
- node.js 上で走らせて、html + css に落とし込めば、ドキュメントが生成できる
- 関数がソースコードに書かれた文字列そのままの形で取り出せるから、ドキュメント上で関数を実行するインタプリタも付けられる
- spec に基づき IN と OUT をチェックする assert を自動生成することも可能
とか考えてました。
jsdoc だと、かゆいところに手が届かないので、こういうことになってしまうわけですが。まぁこれはこれで
Function.prototype.spec の実装はこんな感じ
// Function.prototype.spec - add function spec function Function_spec(hash) { // reserved:{ desc, ret, see, test } Function_spec._spec || (Function_spec._spec = {}, Function_spec._src = {}); var fnName = this.name, // function name i, iz, ary; // pick up function.name [IE6][IE7][IE8][IE9][Opera9][Opera10.1x] if (!fnName) { fnName = this + ""; fnName = fnName.slice(9, fnName.indexOf("(")); // ) } Function_spec._spec[fnName] = hash; Function_spec._src[fnName] = this + ""; if ("test" in hash) { ary = hash.test; for (i = 0, iz = ary.length; i < iz; i += 3) { ary[i].test(ary[i + 1], ary[i + 2] || ""); } } }
通常利用時は {@spec 〜 }@spec コードブロックを削除して minify する感じです。
//{@spec Function.prototype.spec || (Function.prototype.spec = Function_spec); String.prototype.test || (String.prototype.test = String_test); //}@spec
String.prototype.test() は類似検索と深度探索を行う、テスト君です。
http://mofmof-js.googlecode.com/svn/trunk/test/object.js.htm をブラウザで表示し、ブラウザのコンソールを見てみると、(↓)のようなセルフテストの結果が出力されます。これをやってるのが、String#test() です。
これも1つのワンソース・マルチユース。テストもドキュメントもコードに埋め込んでしまえば良いのではないのでしょうか?