webkitTransitionに薄皮かぶせる。
(function(){ var camelize = function(str){ return str.replace(/-+(.)?/g,function(match,chr){ return chr ? chr.toUpperCase():''; }) }; var getPrefixedObject = function(prefix,obj){ var ret = {}; Object.keys(obj).forEach(function(p){ ret[prefix+p] = obj[p]; }); return ret; }; var prefixed = function(prop){ if( /^transition/.test(prop) ) return 'webkit-' + prop; if( /^transform/.test(prop) ) return 'webkit-' + prop; return prop; }; this.setStyle = function(styleObject){ var _self = this; Object.keys(styleObject).forEach(function(p){ _self.style[camelize(prefixed(p))] = styleObject[p]; }); return this; }; this.getStyle = function(name){ return this.style[camelize(prefixed(name))]; }; this.from = this.setStyle; this.to = function(styleObject){ var _self = this; setTimeout(function(){ _self.setStyle(styleObject); },0); return this; }; this.after = function(cb){ var transitionTime = parseFloat(this.getStyle('transition-duration'))*1000; var _self = this; setTimeout(function(){cb.apply(_self)},transitionTime); } }).apply(HTMLElement.prototype);
var overlay = $('overlay'); overlay.from({ "display" : 'block', "height" : (window.innerHeight)+ 'px', "opacity" : 0, "transition-duration" : '1s' }).to({ "opacity":0.5 });
iPhoneでタッチしてスライドする動作を抑制したり戻したりする。
(function(){ var preventDefault = function(e){ e.preventDefault(); }; this.__defineSetter__('enableTouchSlide',function(enableTouchSlide){ this.__enableTouchSlide = enableTouchSlide ? true : false; if( this.__enableTouchSlide ){ this.removeEventListener('touchmove',preventDefault); }else{ this.addEventListener('touchmove',preventDefault); } }); this.__defineGetter__('enableTouchSlide',function(){ return (this.__enableTouchSlide !== false ); }); }).apply(HTMLElement.prototype);
擬似継続ベースの名前空間ライブラリ
http://github.com/hirokidaichi/namespace-js
そんなわけでgithubに上げました。まだgitのことよくわかっていませんw
ドキュメントをなんとなくまとめる
名前空間の定義(define)
Namepsace('org.yabooo.dom').define(function(ns){ // ここにライブラリの実装を書く var SomeModule = function(){}; ns.provide({ SomeModule : SomeModule // exportしたいモジュールを記述 }); }); Namepsace('org.yabooo.net').define(function(ns){ // ここにライブラリの実装を書く var HTTPRequest = function(){}; ns.provide({ HTTPRequest : HTTPRequest // exportしたいモジュールを記述 }); }); // この時点では、実装部分は実行されない。必要になるまで名前空間のデプロイは遅延される。
名前空間の利用(use)
名前空間に存在するコード片を利用したい場合
// exportして使う Namespace('org.yabooo.jsonrpc') .use('org.yabooo.net HTTPRequest') .define(function(ns){ var req = new ns.HTTPRequest; // ns.provide({ JSONRPC : JSONRPC }); }); // fqnで使う Namespace('org.yabooo.jsonrpc') .use('org.yabooo.net') .define(function(ns){ var req = new ns.org.yabooo.net.HTTPRequest; // ns.provide({ JSONRPC : JSONRPC }); }); // withを伴って利用する Namespace('org.yabooo.jsonrpc') .use('org.yabooo.net') .use('org.yabooo.net *') .define(function(ns){with(ns){ var req = new org.yabooo.net.HTTPRequest; var req = new HTTPRequest; // provide({ JSONRPC : JSONRPC }); }}); // この時点では未だどのライブラリも利用の必要性が無いため実行されていない。
名前空間の適応(apply)
Namespace().use('org.yabooo.jsonrpc *').apply(function(ns){ var jsonrpc = js.JSONRPC.createService(); // 実行コード }); //この時点ではじめて必要なライブラリ定義が実行される。
ns.provideで遅延適応させる
ns.provideはdefineの中に必ず存在しなければなりません。
逆に言えば、どれだけあとに実行されても正しく動作します。
Namespace('something.defferred').define(function(ns){ // call 2 setTimeout(function(){ // call 3 ns.provide({ Exports : [] }); },100); }); // call 1 Namespace().use('something.defferred').apply(function(ns){ // call 4 });
そのため、あるイベントが発生してからはじめて有効になるライブラリやそれを用いた処理が簡単に記述できます。
Namespace('something.defferred').define(function(ns){ // call 2 document.addEventListener('DOMContentLoaded',function(){ // call 3 ns.provide({ allAnchors : $$('a') }) }); }); // call 1 Namespace().use('something.defferred').apply(function(ns){ // call 4 });
XHRで外部ライブラリを読み込む
ライブラリを非同期にエクスポートする機能を一番利用するケースは、
必要になるまで読み込まないようなライブラリを利用するケースではないでしょうか。
そんなときのためにサポートユーティリティを用意しています。
Namespace('something.external').define(Namespace.GET('/static/something/external.js')); Namespace().use('something.external').apply(function(ns){ // external.jsを利用するコード });
something/external.jsは次のような感じ
(function(){ var SomeModule = function(){}; return { SomeModule : SomeModule } })();
XHRで読み込んでevalした結果をprovideの中身として扱ってくれます。
ではenjoy!名前空間ライフ
遅延ロード可能バージョン
たとえばこんなふうに
Namespace("org.example.net").define(function(ns){ ns.provide({ HTTPRequest : function(){ console.log("ok");}, HTTPResponse : function(){ console.log("ok");}, }); }); Namespace("org.example.system").define(function(ns){ setTimeout(function(){ ns.provide({ Console : { log : function(item){console.log(item)} }, }); },1000); }); Namespace("org.example.application") .use('org.example.net *') .apply(function(ns){with(ns){ var req = new HTTPRequest; var res = new HTTPResponse; }}); Namespace("org.example.application") .use('org.example.net *') .use('org.example.system Console') .apply(function(ns){with(ns){ var req = new HTTPRequest; var res = new HTTPResponse; Console.log("ok"); }}); Namespace("org.example.application") .use('org.example.net *') .use('org.example.system Console') .apply(function(ns){with(ns){ var req = new HTTPRequest; var res = new HTTPResponse; Console.log("ok"); }});
defineで予約されたものはns.provideを継続として保持している。そのため非同期にns.provideを呼び出しても
use and apply時に順序だって遅延評価される。
たとえば、xhr/createElement('script')で外部のリソースを取得するような場合にも使用できる。
あるいは、DOMContentLoadedまでまた無いとprovideできないようなリソースが存在する場合などにもok。
var Namespace = (function(){ /* utility */ var merge = function(aObj,bObj){ for( var p in bObj ){ if( bObj.hasOwnProperty( p ) ){ aObj[p] = bObj[p]; } } return aObj; }; var _assertValidFQN = function(fqn){ if( !fqn.match(/^[a-z][a-z0-9.]*[a-z0-9]?$/) ){ throw('Invalid namespace'); } }; var Namespace = (function(){ var nsCache = {}; var nsList = []; /* constructor*/ var Klass = function _Private_Class_Of_Namespace(fqn){ this.stash = { CURRENT_NAMESPACE : fqn }; this.name = fqn; this.contexts = []; this.isLoading = false; this.callbacks = []; }; (function(){ var justThrow = function(){ throw('do not call provide twice'); }; this.merge = function(obj){ merge( this.stash,obj); return this; }; this.registerContext = function(context,callback){ this.contexts.push({ context :context , callback : callback }); }; this.load = function(finishedCallback){ this.callbacks.push(finishedCallback); if(!this.isLoading){ this.isLoading = true; this._load(); } }; this._load = function(){ var _self = this; var unused = this.contexts[0]; if( !unused ){ this.isLoading = false; for(var i = 0,l= this.callbacks.length;i<l;i++){ this.callbacks[i](); } this.callbacks=[]; return; } var callback = unused.callback; var context = unused.context; context.load(function(ns){ ns.provide = function(obj){ ns.provide = justThrow; _self.contexts.shift(); _self.merge(obj)._load(); }; callback(ns); }); }; }).apply(Klass.prototype); var _loadStash = function(ns){ if(!nsCache[ns]) throw('undefined namespace:' + ns); return nsCache[ns].stash; }; var _loadStashItem = function(ns,itemName){ var nsStash = _loadStash(ns); if( typeof(nsStash[itemName]) === 'undefined' ) throw('undefined item:' + ns + '#' + itemName); return nsStash[itemName]; } return { loadStash : _loadStash, loadStashItem : _loadStashItem, create :function(fqn){ _assertValidFQN(fqn); if( nsCache[fqn] ) return nsCache[fqn]; var ns = nsCache[fqn] = new Klass(fqn); return ns; } }; })(); var NamespaceContext = (function(){ var Klass = function _Private_Class_Of_NamespaceContext(namespaceObject){ this.namespaceObject = namespaceObject; this.callbacks = []; this.requires = []; this.useList = []; }; var _mergeUseData = (function(){ var _loadImport = function(ns,imports){ var retStash = {}; for(var i = 0,l=imports.length;i<l;i++){ var importSyntax = imports[i]; if( importSyntax === '*' ) return Namespace.loadStash(ns); retStash[importSyntax] = Namespace.loadStashItem(ns,importSyntax); } return retStash; }; var _mergeWithNS = function(stash,ns){ var nsList = ns.split(/\./); var current = stash; for(var i = 0,l=nsList.length;i<l-1;i++){ if( !current[nsList[i]] ) current[nsList[i]] = {}; current = current[nsList[i]]; } var lastLeaf = nsList[nsList.length-1]; if( current[lastLeaf] ) return merge( current[lastLeaf] , Namespace.loadStash(ns) ); return current[lastLeaf] = Namespace.loadStash(ns); }; return function(stash,useData){ if( useData.imports ){ merge( stash , _loadImport(useData.ns,useData.imports)); }else{ _mergeWithNS(stash,useData.ns); } }; })(); (function(){ this.use = function(syntax){ var splittedUseSyntax = syntax.split(/\s/); var fqn = splittedUseSyntax[0]; var imp = splittedUseSyntax[1]; var importNames = (imp) ? imp.split(/,/): null; _assertValidFQN(fqn); this.requires.push(Namespace.create(fqn)); this.useList.push({ ns: fqn,imports: importNames }); return this; }; this.createContextualObject = function(){ var useList = this.useList; var retObj = {}; for(var i=0,l=useList.length;i<l;i++) { _mergeUseData(retObj,useList[i]) } return retObj; }; this.define = function(callback){ this.namespaceObject.registerContext(this,callback); return this.namespaceObject; }; this.load = function(callback){ var _self = this; var require = this.requires.shift(); if( !require ){ return callback( _self.createContextualObject() ) } require.load(function(){ _self.load(callback); }); }; this.apply = function(callback){ var namespaceObject = this.namespaceObject; this.use( namespaceObject.name + ' *'); this.load(function(ns){ callback(ns); }); }; }).apply(Klass.prototype); return Klass; })(); return function(nsString){ return new NamespaceContext(Namespace.create(nsString)); }; })();
普通のバージョン。
Namespace("org.example.net").apply(function(ns){ return { HTTPRequest : function(){ console.log("ok");}, HTTPResponse : function(){ console.log("ok");}, }; }); Namespace("org.example.system").apply(function(ns){ return { Console : { log : function(item){console.log(item)} }, }; }); Namespace("org.example.application") .use('org.example.net *') .apply(function(ns){with(ns){ var req = new HTTPRequest; var res = new HTTPResponse; }}); Namespace("org.example.application") .use('org.example.net *') .use('org.example.system Console') .apply(function(ns){with(ns){ var req = new HTTPRequest; var res = new HTTPResponse; Console.log("ok"); }}); Namespace("org.example.application") .use('org.example.net') .use('org.example.system') .apply(function(ns){with(ns){ var req = new org.example.net.HTTPRequest; var res = new org.example.net.HTTPResponse; org.example.system.Console.log("ok"); }});
こんなふうに書きたくなったので、つくった。
var Namespace = (function(){ /* utility */ var merge = function(aObj,bObj){ for( var p in bObj ){ if( bObj.hasOwnProperty( p ) ){ aObj[p] = bObj[p]; } } return aObj; }; /* inner class of namespace*/ var _Namespace = (function(){ var nsCache = {}; var nsList = []; /* constructor*/ var Klass = function _Private_Class_Of_Namespace(fqn){ this._useList = []; this._stash = { CURRENT_NAMESPACE : fqn }; }; /* private functions */ var _loadStash = function(ns){ if(!nsCache[ns]) throw('undefined namespace:' + ns); return nsCache[ns]._stash; }; var _loadStashItem = function(ns,itemName){ var nsStash = _loadStash(ns); if(typeof(nsStash[itemName]) === 'undefined') throw('undefined item:' +ns+'#'+itemName); return nsStash[itemName]; }; var _mergeUseData = (function(){ var _loadImport = function(ns,imports){ var retStash = {}; for(var i = 0,l=imports.length;i<l;i++){ var importSyntax = imports[i]; if( importSyntax === '*' ) return _loadStash(ns); retStash[importSyntax] = _loadStashItem(ns,importSyntax); } return retStash; }; var _mergeWithNS = function(stash,ns){ var nsList = ns.split(/\./); var current = stash; for(var i = 0,l=nsList.length;i<l-1;i++){ if( !current[nsList[i]] ) current[nsList[i]] = {}; current = current[nsList[i]]; } current[nsList[nsList.length-1]] = _loadStash(ns); }; return function(stash,useData){ if( useData.import ){ merge( stash , _loadImport(useData.ns,useData.import)); }else{ _mergeWithNS(stash,useData.ns); } }; })(); var _assertValidFQN = function(fqn){ if( !fqn.match(/^[a-z][a-z0-9.]+[a-z0-9]$/) ){ throw('Invalid namespace'); } }; /* extend prototype */ (function(){ var _composeStash = function(that){ var stash = {}; for(var i = 0,l= that._useList.length;i<l;i++) _mergeUseData( stash, that._useList[i]); return merge( stash , that._stash ); }; this.use = function(){ for(var i = 0,l=arguments.length;i<l;i++){ var splittedUseSyntax = arguments[i].split(/\s/); var fqn = splittedUseSyntax[0]; var imp = splittedUseSyntax[1]; var importNames = (imp) ? imp.split(/,/): null; _assertValidFQN(fqn); this._useList.push({ ns: fqn,import: importNames }); } return this; }; this.apply = function(callback){ merge(this._stash,callback(_composeStash(this))||{}); this._useList = []; return this; }; this.define = function(obj){ return this.apply(function(){return obj}); }; }).apply(Klass.prototype); /* public interface */ return { create :function(fqn){ _assertValidFQN(fqn); if( nsCache[fqn] ){ return nsCache[fqn]; } var ns = nsCache[fqn] = new Klass(fqn); return ns; } }; })(); return function(nsString){ return _Namespace.create(nsString); }; })();
HTML 5 data- Attributes
こんなかんじ?
var $D = (function(){ var camelize = function(string){ return string.replace(/-+(.)?/g, function(match, chr) { return chr ? chr.toUpperCase() : ''; }); }; return function(element){ if( element.dataset ) return element.dataset; var sets = {}; for(var i=0,a=element.attributes,l=a.length;i<l;i++){ var attr = a[i]; if( !attr.name.match(/^data-/) ) continue; sets[camelize(attr.name.replace(/^data-/,''))] = attr.value; } return sets; }; })();
<li id="xyz" data-length="1" data-foo-bar='1' data-something="hello">test</li>
alert(JSON.stringify( $D(document.getElementById('xyz')) )); // { length:1 ,fooBar:1,something:"hello" }