ukijs

 ukijsというJavaScriptのUIライブラリがあるそうで、見た目はとってもヨサゲです。サンプルも結構あります。


おそらく、これが一番カンタンなサンプルだと思います。

/**
@example_title Hello world
@example_order 1
@example_html
<div id='test' style='width: 50%; height: 100px; background: #EEE'>#test</div>
<script src="/src/uki.cjs"></script>
<script src="attachment.js"></script>
*/

uki({
    view: 'Button',
    rect: '200 40 200 24',
    text: 'uki is awesome!'
}).attachTo( document.getElementById('test'), '600 100' );

uki('Button[text^=uki]').bind('click', function() {
    alert('Hello world!');
});

ukijsのコードを読む

残念なことにukijsには、ドキュメントがあまりありません。
仕方がないので、慣れないJavaScriptを読んでみました。

読んだソースは、uki.dev.jsの0.3.8です。

uki関数(viewの作成・検索)

以下は、サンプル・ソースでエントリポイントになっているuki関数のコードです。

/**
 * Shortcut access to uki.build, uki.Selector.find and uki.Collection constructor
 * uki('#id') is also a shortcut for search by id
 *
 * @param {String|uki.view.Base|Object|Array.<uki.view.Base>} val
 * @param {Array.<uki.view.Base>=} optional context for selector
 * @class
 * @namespace
 * @name uki
 * @return {uki.Collection}
 */
root.uki = root.uki || function(val, context) {
    if (typeof val === "string") {
	
        var m = val.match(/^#((?:[\w\u00c0-\uFFFF_-]|\\.)+)$/),
            e = m && uki._ids[m[1]];
        if (m && !context) {
            return new uki.Collection( e ? [e] : [] );
        }
        return uki.find(val, context);
		
    }
    if (val.length === undefined) val = [val];
    if (val.length > 0 && uki.isFunction(val[0].typeName)) return new uki.Collection(val);
	
    return uki.build(val);
};

uki関数は、引数valに文字列を渡すとエレメントを検索します。文字列はCSSセレクタ形式のようです(未確認)。id形式の文字列('#hoge'とか)を渡すと、uki._idsに格納されているエレメントを返します。uki._idsには、uki.registerを使って登録したものしか入っていないので、HTMLで直接idを指定したエレメントは、検索対象に入りません。文字列がid形式でない場合やcontext(何のことだか調べてません)が指定された場合には、uki.findを呼出した結果を返します。

引数valが文字列でない場合には、uki.buildを呼出してエレメント(複数可)を作成します。
この場合、valに与えるのはマップかマップの配列(JSON風のマークアップ、っていうことらしい)のようです。


次はuki.buildのコードです。
引数mlが配列でなければ、配列でくるんだ上でcreateMultiに渡し、結果をuki.Collectionにまとめて戻します。
createMulti関数は、mapを使ってcreateSingleを繰り返し呼出した結果をまとめています。

    /**
     * Creates uki view tree from JSON-like markup
     *
     * @example
     * uki.build( {view: 'Button', rect: '100 100 100 24', text: 'Hello world' } )
     * // Creates uki.view.Button with '100 100 100 24' passed to constructor, 
     * // and calls text('Hello world') on it
     *
     * @function
     * @name uki.build
     *
     * @param {object} ml JSON-like markup
     * @returns {uki.view.Collection} collection of created elements
     */
    uki.build = function(ml) {
        
        return new uki.Collection( createMulti( (ml.length === undefined) ? [ml] : ml ) );
		
    };

    function createMulti (ml) {
        return uki.map(ml, function(mlRow) { return createSingle(mlRow); });
    }


こちらはcreateSingleのコードです。
実際にエレメントを作成するのは、ここでやっているようです。
この関数には、uki関数に渡されたマップ配列の1要素がmlRowとして渡されます。

最初のif文で、uki.isFunctionを使ったチェックが行われていますが、この動作は調べていません。
サンプルでは、ここに入ってくる使われ方はしていないようです。(全部見た訳じゃありませんが)

サンプルの使い方では、mlRowのview属性あるいはtype属性に指定された文字列が、作成されるエレメントのコンストラクタ名になります。そこに、rect属性の値を引数として渡し、新たなエレメントを作成します。

    function createSingle (mlRow) {
        if (uki.isFunction(mlRow.typeName)) {
            return mlRow;
        }

        var c = mlRow.view || mlRow.type,
            result;
        if (uki.isFunction(c)) {
            result = new c(mlRow.rect);
        } else if (typeof c === 'string') {
            for (var i=0, ns = uki.viewNamespaces, ns$length = ns.length; i < ns$length; i++) {
                var parts = (ns[i] + c).split('.'),
                    obj = root;
                
                for (var j=0, parts$length = parts.length; obj && j < parts$length; j++) {
                    obj = obj[parts[j]];
                };
                if (obj) {
                    result = new obj(mlRow.rect);
                    break;
                }
            };
            if (!obj) throw 'No view of type ' + c + ' found';
        } else {
            result = c;
        }

        copyAttrs(result, mlRow);
        return result;
    }


mlRowの残りの属性は、copyAttrs関数で作成したエレメントに渡されます。

    function copyAttrs(comp, mlRow) {
        uki.each(mlRow, function(name, value) {
            if (name == 'view' || name == 'type' || name == 'rect') return;
            uki.attr(comp, name, value);
        });
        return comp;
    }
attachTo関数

uki.build関数では、エレメントの情報を持ったクラスのインスタンスを作成しただけなので、まだブラウザ上には何もでてきません。実際にブラウザ上にエレメントを表示させるには、DOMを操作する必要があります。attachTo関数がこれを行います。


以下は、ViewのattachTo関数のコードです。
中身は、uki.Attachmentクラスをnewして返しているだけです。

    /**
     * Attaches all child views to dom container
     *
     * @function
     *
     * @param {Element} dom Container dom element
     * @param {uki.geometry.Rect} rect Default size
     * @returns {uki.view.Collection} self
     */
    this.attachTo = function( dom, rect ) {
        this.each(function() {
            new uki.Attachment( dom, this, rect );
        });
        return this;
    };

以下は、uki.Attachmentのコンストラクタのコードです。

        /**
         * Attachment serves as a connection between a uki view and a dom container.
         * It notifies its view with parentResized on window resize. 
         * Attachment supports part of uki.view.Base API like #domForChild or #rectForChild
         *
         * @param {Element} dom Container element
         * @param {uki.view.Base} view Attached view
         * @param {uki.geometry.Rect} rect Initial size
         *
         * @see uki.view.Base#parentResized
         * @name uki.Attachment
         * @augments uki.view.Observable
         * @constructor
         */
        init: function( dom, view, rect ) {
            uki.initNativeLayout();
            
            this._dom     = dom = dom || root;
            this._view    = view;
            this._rect = Rect.create(rect) || this.rect();
            
            uki.dom.offset.initialize();
            
            view.parent(this);
            this.domForChild().appendChild(view.dom());
            
            if (dom != root && dom.tagName != 'BODY') {
                var computedStyle = dom.runtimeStyle || dom.ownerDocument.defaultView.getComputedStyle(dom, null);
                if (!computedStyle.position || computedStyle.position == 'static') dom.style.position = 'relative';
            }
            self.register(this);

            this.layout();
        },

あらかじめ、viewのコンストラクタで作成されたエレメントを、DOMに追加しているのは、次のコードです。

this.domForChild().appendChild(view.dom());

各Viewのスタイルは、background属性で設定します。background属性には、uki.background.Cssやuki.background.CssBoxを直接指定することができます。これらのクラスをDOMに反映するのも、やはりattchTo関数です。

以下は、uki.background.CssのattachTo関数のコードです。

    this.attachTo = function(comp) {
        this._comp = comp;
        this._originalValues = {};
        
        uki.each(this._options, function(name, value) {
            // this._originalValues[name] = dom.style[name];
            // dom.style[name] = value;
            this._originalValues[name] = comp.style(name);
            comp.style(name, value);
        }, this);
    };


uki.background.CssBoxのattachTo関数のコードは、次のようになります。

    this.attachTo = function(comp) {
        this._comp = comp;
        this._comp.dom().insertBefore(this._container, this._comp.dom().firstChild);

        if (uki.supportNativeLayout) return;
        
        this._layoutHandler = this._layoutHandler || uki.proxy(function(e) { this.layout(e.rect); }, this);
        this._comp.bind('layout', this._layoutHandler);
        this.layout(this._comp.rect());
    };
    
    this.layout = function(size) {
        this._prevLayout = uki.dom.layout(this._container.style, {
            width: size.width - this._insetWidth,
            height: size.height - this._insetHeight
        }, this._prevLayout);
    };