From 2956150a7c2798e60639b36d69b0c13f6b20a62a Mon Sep 17 00:00:00 2001 From: itozyun Date: Tue, 19 May 2015 19:48:07 +0900 Subject: [PATCH] Version 0.6.149, fix X.Audio & X.UI. --- 0.6.x/js/01_core/03_XType.js | 33 +- 0.6.x/js/01_core/09_XPair.js | 2 +- 0.6.x/js/01_core/11_XClass.js | 191 +++------- 0.6.x/js/01_core/12_XEvent.js | 10 +- 0.6.x/js/01_core/13_XEventDispatcher.js | 164 ++++----- 0.6.x/js/01_core/16_XViewPort.js | 27 +- 0.6.x/js/02_dom/00_XDoc.js | 27 +- 0.6.x/js/02_dom/05_XNodeAttr.js | 19 +- 0.6.x/js/02_dom/06_XNodeCSS.js | 18 +- 0.6.x/js/02_dom/08_XNodeSelector.js | 11 +- 0.6.x/js/02_dom/10_XNodeAnime.js | 30 +- 0.6.x/js/03_plugin/00_XPlugin.js | 4 +- 0.6.x/js/05_util/04_XXML.js | 575 ++++++++++++++++++++++++++++++ 0.6.x/js/06_net/00_XNet.js | 47 ++- 0.6.x/js/06_net/01_XNetXHR.js | 51 ++- 0.6.x/js/06_net/05_XXHRGadget.js | 176 +++++++++ 0.6.x/js/06_net/10_XOAuth2.js | 312 ++++++++++++++++ 0.6.x/js/07_audio/00_XAudio.js | 383 ++++++++++++-------- 0.6.x/js/07_audio/01_XWebAudio.js | 291 ++++++++------- 0.6.x/js/07_audio/02_XHTMLAudio.js | 191 ++++------ 0.6.x/js/07_audio/03_XSilverlightAudio.js | 293 +++++++-------- 0.6.x/js/07_audio/10_XAudioSprite.js | 310 ++++++++-------- 0.6.x/js/20_ui/00_XUI.js | 24 -- 0.6.x/js/20_ui/02_XUI_Attr.js | 23 ++ 0.6.x/js/20_ui/06_AbstractUINode.js | 32 +- 0.6.x/js/20_ui/08_Box.js | 38 +- 0.6.x/js/20_ui/11_VBox.js | 46 ++- 0.6.x/js/20_ui/12_HBox.js | 29 +- 0.6.x/js/20_ui/13_TileBox.js | 30 +- 0.6.x/js/20_ui/14_ChromeBox.js | 37 +- 0.6.x/js/20_ui/15_ScrollBox.js | 47 +-- 0.6.x/js/20_ui/17_Text.js | 15 +- 0.6.x/js/20_ui/20_PageRoot.js | 33 +- 33 files changed, 2368 insertions(+), 1151 deletions(-) create mode 100644 0.6.x/js/05_util/04_XXML.js create mode 100644 0.6.x/js/06_net/05_XXHRGadget.js create mode 100644 0.6.x/js/06_net/10_XOAuth2.js diff --git a/0.6.x/js/01_core/03_XType.js b/0.6.x/js/01_core/03_XType.js index a7a78aa..ed024ae 100644 --- a/0.6.x/js/01_core/03_XType.js +++ b/0.6.x/js/01_core/03_XType.js @@ -1,10 +1,9 @@ -var /** * Array か?判定する。argumnets 等のフェイク Array は false なので注意。 - * @funciton - * @alias X.Type._isArray + * @function + * @alias X.Type.isArray */ - X_Type_isArray = +var X_Type_isArray = new Function( 'v', X_UA[ 'IE' ] < 5.5 || X_UA[ 'NetFront' ] < 4 ? // netfront3.4 は html に instanceof をすると error になる 'return v&&v.push===Array.prototype.push' : // win ie5-, MacIE5.2 @@ -15,13 +14,13 @@ var /** * HTMLElement か?判定する。ちなみに return v instanceof Element は ie8 でエラー。 - * @funciton + * @function * @alias X.Type.isHTMLElement */ X_Type_isHTMLElement = new Function( 'v', ( X_UA[ 'IE4' ] || X_UA[ 'MacIE' ] ) ? - 'return v&&v.tagName&&v.insertAdjacentHTML&&true' : // ie4 or MacIE5.23, v.all <- error + 'return v&&v.tagName&&v.insertAdjacentHTML&&!0' : // ie4 or MacIE5.23, v.all <- error X_UA[ 'NetFront' ] < 4 ? 'return v&&v.nodeType===1' : // instanceof not a function. netfront3.4 は html に instanceof をすると error になる window[ 'HTMLElement' ] ? @@ -32,9 +31,8 @@ var /** - * http://pettanr.sourceforge.jp/test/type.html - * ビルトイン方の判定に使用する関数を集めたもの。ブラウザのネイティブな判定関数には不可解な挙動があるので、X.Type を使用するほうがよい。 - * + *

ビルトイン型の判定に使用する関数を集めたもの。ブラウザのネイティブな判定関数には不可解な挙動があるので、X.Type を使用するほうがよい。 + * http://pettanr.sourceforge.jp/test/type.html * @namespace X.Type * @alias X.Type */ @@ -55,12 +53,13 @@ X[ 'Type' ] = { }; /** - * Object か?判定する。typeof null === 'object' に対策済なので null は Object ではない。 - * new String(), new Number(), new Boolean() も typeof object なので対策 + *

Object か?判定する。 + *

typeof null === 'object' に対策済なので null は Object ではない。 + *

new String(), new Number(), new Boolean() も typeof object なので対策 * @alias X.Type.isObject */ function X_Type_isObject( v ){ - return v && typeof v === 'object'; // && ( v !== v + '' && v !== v + 0 && v !== true ) ; // typeof null === 'object' に対策 + return v && typeof v === 'object' && v !== v + '' && v !== v + 0 && v !== true; // typeof null === 'object' に対策 }; /** * Function か?判定する。 @@ -89,14 +88,14 @@ X[ 'Type' ] = { * @alias X.Type.isString */ function X_Type_isString( v ){ - return typeof v === 'string'; // v === v + ''; // 文字列の加算は IE で遅いかも。 + return /* typeof v === 'string'; */ v === v + ''; // 文字列の加算は IE で遅いかも。 }; /** - * 数値値か?判定する。 + * 数値か?判定する。 * @alias X.Type.isNumber */ function X_Type_isNumber( v ){ - return typeof v === 'number'; // v !== v || v + 0 === v; + return /* typeof v === 'number'; */ v + 0 === v || v !== v; }; /** * finite か?判定する。isFinite( '123' ) === true に対策済。 @@ -120,7 +119,7 @@ X[ 'Type' ] = { */ function X_Type_isImage( v ){ if( v && v.constructor === window.Image ) return true; - if( v && window.HTMLImageElement && v.constructor === window.HTMLImageElement ) return true; // ie6- は constructor が undef、HTMLImageElement が undef なので、HTMLElement の存在確認が必要 + if( v && window.HTMLImageElement && v.constructor === window.HTMLImageElement ) return true; // ie6- は constructor が undef、HTMLImageElement が undef なので、HTMLElement の存在確認が必要 if( X_UA[ 'WebKit' ] < 525.13 ){ // Safari3- if( v && v.src !== undefined && v.onload !== undefined && X_Type_isNumber( v.height ) && X_Type_isNumber( v.width ) && X_Type_isBoolean( v.complete ) ){ return true; @@ -145,7 +144,7 @@ X[ 'Type' ] = { * @alias X.Type.isUndefined */ function X_Type_isUndefined( v ){ - return v === void 0; + return v === undefined; }; console.log( 'X.Core.Type' ); diff --git a/0.6.x/js/01_core/09_XPair.js b/0.6.x/js/01_core/09_XPair.js index 60fb3dc..120258c 100644 --- a/0.6.x/js/01_core/09_XPair.js +++ b/0.6.x/js/01_core/09_XPair.js @@ -31,7 +31,7 @@ function X_Pair_create( key, pair ){ pairStore = X_Pair_PAIR_STORE_LIST[ X_Pair_PAIR_STORE_LIST.length - 1 ]; X_Pair_noChashe = true; - if( X_Pair_get( key ) ) return; + if( X_Pair_get( key ) || !( X_Type_isObject( key ) || X_Type_isArray( key ) || X_Type_isFunction( key ) ) ) return; if( keyStore.length === X_Pair_SIZE ){ keyStore = X_Pair_KEY_STORE_LIST[ X_Pair_KEY_STORE_LIST.length ] = []; diff --git a/0.6.x/js/01_core/11_XClass.js b/0.6.x/js/01_core/11_XClass.js index be37dc2..cbaeb22 100644 --- a/0.6.x/js/01_core/11_XClass.js +++ b/0.6.x/js/01_core/11_XClass.js @@ -20,11 +20,8 @@ var X_Class_CLASS_LIST = [], X_Class_DEF_LIST = [], - X_Class_PRIVATE_CLASS_LIST = [], - X_Class_PRIVATE_DEF_LIST = [], X_Class_CALLING_SUPER = [], X_Class_CALL_SUPER_STACK = [], - X_Class_killPrivateFlag = false, X_Class_traits = null, X_Class_useObjectCreate = false, // !!Object.create, http://jsperf.com/prototype-vs-object-create-perf X_Class_use_proto_ = !X_UA[ 'OperaMobile' ] && !X_UA[ 'OperaTablet' ] && !!X_emptyFunction.prototype.__proto__, @@ -48,26 +45,18 @@ X_Class_CommonMethods = klass = X_Class_getClass( instance ), def = X_Class_getClassDef( klass ), data, p, i; - if( def.isPrivate && !X_Class_killPrivateFlag && ( !this[ '_listeners' ] || !this[ '_listeners' ][ X_Listeners_.KILL_RESERVED ] ) ){ - X.Logger.critical( 'PrivateInstance.kill() work in PrivateUser.kill().' ); - return; - }; - X_Class_killPrivateFlag = false; // instance.kill() 内で PrivateInstance.kill() を防ぐため // TODO kill 中の kill の呼び出しを防ぐ, 破棄済のインスタンスへの kill if( this[ 'instanceOf' ]( X_EventDispatcher ) ){ - if( !def.isPrivate ){ - if( this[ 'dispatch' ]( X_EVENT_BEFORE_KILL_INSTANCE ) & X_Callback_PREVENT_DEFAULT ){ - this[ 'dispatch' ]( X_EVENT_KILL_INSTANCE_CANCELED ); - return; - }; - if( this[ '_listeners' ] && this[ '_listeners' ][ X_Listeners_.DISPATCHING ] ){ - this[ '_listeners' ][ X_Listeners_.KILL_RESERVED ] = true; - return; - }; - } else { - this[ 'dispatch' ]( X_EVENT_BEFORE_KILL_INSTANCE ); + + if( this[ 'dispatch' ]( X_EVENT_BEFORE_KILL_INSTANCE ) & X_Callback_PREVENT_DEFAULT ){ + this[ 'dispatch' ]( X_EVENT_KILL_INSTANCE_CANCELED ); + return; + }; + if( this[ '_listeners' ] && this[ '_listeners' ][ X_LISTENERS_DISPATCHING ] ){ + this[ '_listeners' ][ X_LISTENERS_KILL_RESERVED ] = true; + return; }; // asyncDispatch の削除 @@ -90,20 +79,6 @@ X_Class_CommonMethods = def.live && def.live.splice( def.live.indexOf( instance ), 1 ); def.pool[ def.pool.length ] = instance; }; - if( def.privateClass ){ - i = def.userList.indexOf( instance ); - if( i !== -1 ){ - data = X_Class_getPrivate( instance ); - if( data[ '_listeners' ] && data[ '_listeners' ][ X_Listeners_.DISPATCHING ] && data[ 'instanceOf' ]( X.EventDispatcher ) ){ - data[ '_listeners' ][ X_Listeners_.KILL_RESERVED ] = true; - } else { - X_Class_killPrivateFlag = true; - data[ 'kill' ](); - }; - def.dataList.splice( i, 1 ); - def.userList.splice( i, 1 ); - }; - }; }, /** @@ -234,9 +209,7 @@ var X_Class = { POOL_OBJECT : 1, ABSTRACT : 2, FINAL : 4, - SUPER_ACCESS : 8, - PRIVATE_DATA : 16, - SINGLETON : 32 + SINGLETON : 8 }; /** @@ -250,7 +223,7 @@ var X_Class = { * * *

    - *
  1. X_Class.create( opt_settings, opt_name, opt_privateClass, opt_props ) でクラスを登録. + *
  2. X.Class.create( opt_settings, opt_name, opt_props ) でクラスを登録. *
  3. コンストラクタ となるメソッドは、opt_props 内の Constructor : function( arg ){ ... }, に書く. *
  4. 通常通り new で インスタンス生成 *
  5. kill() でオブジェクトをクリーンして削除、pool が有効の場合は pool される. @@ -259,7 +232,7 @@ var X_Class = { * @namespace X.Class * @alias X.Class */ -X[ 'Class' ] = { +X[ 'Class' ] = /** @lends X.Class */ { /** * 設定なし。 @@ -286,32 +259,14 @@ X[ 'Class' ] = { 'FINAL' : X_Class.FINAL, /** - * 使用を中止。petanR ライブラリ使用プロジェクトから SUPER_ACCESS を消したらここも削除。 - * @const - */ - 'SUPER_ACCESS' : X_Class.SUPER_ACCESS, - - /** - * 内部コード、主に X.UI フレームワークに対して、フレーム外に露出するインスタンスとペアで動作する、シャドウなインスタンスの使用を宣言する。 - * Javascript はインスタンス毎のカプセル化がとてもコスト高。微妙なコスト増で隠蔽されたインスタンスを使う。 - * @const - */ - 'PRIVATE_DATA' : X_Class.PRIVATE_DATA, - - /** * 未実装。でも目印になるので付けておきましょう。 * @const */ 'SINGLETON' : X_Class.SINGLETON, - 'create' : X_Class_create, + 'create' : X_Class_create // TODO collect - - '_newPrivate' : X_Class_newPrivate, - - '_getPrivate' : X_Class_getPrivate - }; @@ -321,19 +276,34 @@ X[ 'Class' ] = { // ------------------------------------------------------------------------- // /** * クラスを定義する。
    - * X_Class.create() によるクラス定義は必ずしもコンストラクタを必要としません。クラス定義時にコンストラクタが未設定の場合、スーパークラスがあればそのコンストラクタを使用します。 - * @alias X_Class.create + * X.Class.create() によるクラス定義は必ずしもコンストラクタ('Constructor')を必要としません。クラス定義時にコンストラクタが未設定の場合、スーパークラスがあればそのコンストラクタを使用します。 + * @alias X.Class.create * @param {string} [displayName] クラスの名前 * @param {number} [classSetting=0] X_Class.POOL_OBJECT | X_Class.FINAL など - * @param {__ClassBase__=} [privateClass] このクラスとペアで動作するシャドウクラス * @param {object} [props={}] このクラスのメンバと関数。コンストラクタは Constructor と書くこと * @return {__ClassBase__} + * @example var myClass = X.Class.create( + * 'myClass', + * X.Class.FINAL, + * { + * name : '', + * Constructor : function( obj ){ + * this.name = obj.name; + * }, + * getName : function(){ + * return this.name; + * }, + * setName : function(v){ + * this.name = v; + * } + * } + * ); */ function X_Class_create( /* displayName, classSetting, privateClass, props */ ){ var args = X_Object_cloneArray( arguments ), displayName = args[ 0 ], classSetting, - opt_pool, opt_abstract, opt_final, opt_private, + opt_pool, opt_abstract, opt_final, privateDef, props, klass, @@ -349,7 +319,6 @@ X[ 'Class' ] = { opt_pool = !!( classSetting & X_Class.POOL_OBJECT ); opt_abstract = !!( classSetting & X_Class.ABSTRACT ); opt_final = !!( classSetting & X_Class.FINAL ); - opt_private = !!( classSetting & X_Class.PRIVATE_DATA ); if( opt_final && opt_abstract ){ X.Logger.critical( 'final & Abstract!' ); return; @@ -359,20 +328,6 @@ X[ 'Class' ] = { classDef.setting = 0; }; - // シャドウクラス - if( X_Class_PRIVATE_CLASS_LIST.indexOf( args[ 0 ] ) !== -1 ){ - privateDef = X_Class_getClassDef( args[ 0 ] ); - if( privateDef.isPrivate !== true ){ - X.Logger.critical( 'PrivateClass not found! please, X_Class.create( X_Class.PRIVATE, {...} ).' ); - return; - } else - if( privateDef.Abstract === true ){ - X.Logger.critical( 'PrivateClass is Abstract!' ); - return; - }; - classDef.privateClass = args.shift(); - }; - // インスタンスのメンバー props = args[ 0 ]; if( !X_Type_isObject( props ) ){ @@ -411,25 +366,17 @@ X[ 'Class' ] = { } else if( opt_pool ){ classDef.pool = []; - if( opt_private === false ) classDef.live = []; + classDef.live = []; }; if( opt_final ){ classDef.Final = true; } else { klass[ 'inherits' ] = X_Class_inherits; }; - if( opt_private ){ - if( classDef.privateClass ){ - X.Logger.critical( 'Private Data Class has no PrivateClass!' ); - return; - }; - classDef.isPrivate = true; - X_Class_PRIVATE_CLASS_LIST.push( klass ); - X_Class_PRIVATE_DEF_LIST.push( classDef ); - } else { - X_Class_CLASS_LIST.push( klass ); - X_Class_DEF_LIST.push( classDef ); - }; + + X_Class_CLASS_LIST.push( klass ); + X_Class_DEF_LIST.push( classDef ); + return klass; }; @@ -443,15 +390,7 @@ function X_Class_getClass( instance ){ klass = cList[ --i ]; if( instance.constructor === klass ) return klass; }; - cList = X_Class_PRIVATE_CLASS_LIST; - i = cList.length; - for( ; i; ){ - klass = cList[ --i ]; - if( instance.constructor === klass ) return klass; - }; - if( cList.indexOf( instance ) !== -1 ) return instance; - if( X_Class_CLASS_LIST.indexOf( instance ) !== -1 ) return instance; }; function X_Class_getClassDef( KlassOrInstance ){ @@ -459,43 +398,7 @@ function X_Class_getClassDef( KlassOrInstance ){ if( i === -1 ) i = X_Class_CLASS_LIST.indexOf( X_Class_getClass( KlassOrInstance ) ); if( i !== -1 ) return X_Class_DEF_LIST[ i ]; - i = X_Class_PRIVATE_CLASS_LIST.indexOf( KlassOrInstance ); - if( i === -1 ) i = X_Class_PRIVATE_CLASS_LIST.indexOf( X_Class_getClass( KlassOrInstance ) ); - if( i !== -1 ) return X_Class_PRIVATE_DEF_LIST[ i ]; - if( X_Class_DEF_LIST.indexOf( KlassOrInstance ) !== -1 ) return KlassOrInstance; - if( X_Class_PRIVATE_DEF_LIST.indexOf( KlassOrInstance ) !== -1 ) return KlassOrInstance; -}; - -function X_Class_newPrivate( /* instance, args */ ){ - var args = X_Object_cloneArray( arguments ), - user = args.shift(), - def = X_Class_getClassDef( user ), - privateClass = def.privateClass, - privateDef = X_Class_getClassDef( privateClass ), - i = -1; - if( def.userList ){ - i = def.userList.indexOf( user ); - } else { - def.userList = []; - def.dataList = []; - }; - if( i !== -1 ){ - X.Logger.critical( 'PrivateData already exist!' ); - return; - }; - if( privateDef._tempUser ){ - X.Logger.critical( 'newPrivate を連続呼び出しされたところ破綻' ); - return; - }; - privateDef._tempUser = user; - return X_Class_actualConstructor( privateClass( X_Closure_COMMAND_BACK ), args );// privateClass.__new( args ); -}; - -function X_Class_getPrivate( instance ){ - var def = X_Class_getClassDef( instance ), - i = def.userList.indexOf( instance ); - if( i !== -1 ) return def.dataList[ i ]; }; /* over のプロパティを target にコピーする.ただし target の プロパティが優先, force で解除 */ @@ -582,16 +485,12 @@ function X_Class_inherits( /* displayName, classSetting, opt_PrivateClass, props // クラス設定がない場合、親からコピーして、Abstract flag は落とす?? classSetting = superDef.setting;// &= ~X_Class.ABSTRACT; }; - if( superDef.isPrivate ) classSetting = classSetting | X_Class.PRIVATE_DATA; params.push( classSetting ); // サブクラスのシャドウ if( args[ 0 ] && X_Class_getClass( args[ 0 ] ) ){ params.push( args.shift() ); - } else - if( superDef.privateClass ){ - params.push( superDef.privateClass ); }; /* props 未定義でも可 */ @@ -628,14 +527,11 @@ function X_Class_actualConstructor( f, args ){ dataUser = def._tempUser, instance, obj, userDef; + if( def.Abstract ){ X.Logger.critical( 'AbstractClass!' ); return; }; - if( def.isPrivate && !dataUser ){ - X.Logger.critical( 'use myClass.newPrivate( instance, ...args )!' ); - return; - }; instance = def.pool && def.pool.length > 0 ? def.pool.pop() : @@ -643,21 +539,14 @@ function X_Class_actualConstructor( f, args ){ Object.create( klass.prototype ) : new klass( X_Closure_COMMAND_DROP ); - if( def.isPrivate ){ - userDef = X_Class_getClassDef( dataUser ); - userDef.dataList.push( instance ); - userDef.userList.push( dataUser ); - instance.User = dataUser; - def._tempUser = null; - } else { - def.live && def.live.push( instance ); - }; + def.live && def.live.push( instance ); obj = def.Constructor ? def.Constructor.apply( instance, args ) : def.SuperConstructor && def.SuperConstructor.apply( instance, args ); - if( ( X_Type_isObject( obj ) && obj !== instance ) || X_Type_isFunction( obj ) ){ // Class + + if( obj !== instance && ( X_Type_isObject( obj ) || X_Type_isFunction( obj ) ) ){ // Class instance[ 'kill' ](); return obj; }; diff --git a/0.6.x/js/01_core/12_XEvent.js b/0.6.x/js/01_core/12_XEvent.js index 5074db7..8b1c278 100644 --- a/0.6.x/js/01_core/12_XEvent.js +++ b/0.6.x/js/01_core/12_XEvent.js @@ -50,7 +50,7 @@ var X_Event_Rename = {}, }); var // 内部イベント - X_EVENT_PRE_INIT = 5, // X_Listeners_.KILL_RESERVED に +1 した値から開始。 + X_EVENT_PRE_INIT = 5, // X_LISTENERS_KILL_RESERVED に +1 した値から開始。 X_EVENT_XTREE_READY = 6, X_EVENT_INIT = 7, @@ -113,7 +113,9 @@ var // 内部イベント X_EVENT_MEDIA_WAITING = 47, X_EVENT_MEDIA_SEEKING = 48, - X_Event_last = 48; + X_EVENT_NEED_AUTH = 49, + + X_Event_last = 49; /** * フレームワーク内で定義されたイベント。 @@ -223,7 +225,9 @@ X[ 'Event' ] = { 'MEDIA_PAUSED' : X_EVENT_MEDIA_PAUSED, 'MEDIA_ENDED' : X_EVENT_MEDIA_ENDED, 'MEDIA_WAITING' : X_EVENT_MEDIA_WAITING, - 'MEDIA_SEEKING' : X_EVENT_MEDIA_SEEKING + 'MEDIA_SEEKING' : X_EVENT_MEDIA_SEEKING, + + 'NEED_AUTH' : X_EVENT_NEED_AUTH }; X_TEMP.onSystemReady.push( diff --git a/0.6.x/js/01_core/13_XEventDispatcher.js b/0.6.x/js/01_core/13_XEventDispatcher.js index 612268f..f798c2a 100644 --- a/0.6.x/js/01_core/13_XEventDispatcher.js +++ b/0.6.x/js/01_core/13_XEventDispatcher.js @@ -10,7 +10,7 @@ * *
    *
    0:ACTUAL_HANDLER - *
    イベントターゲットの addEventListener 等に渡される実際の関数(再利用可能クロージャ)を控えています。 + *
    イベントターゲットの addEventListener 等に渡される実際の関数(多くの場合、再利用可能クロージャ、それ以外は通常の関数)を控えています。 *
    1:DISPATCHING number *
    dispatch 中か?さらにインスタンス自身の dispatch がネストした場合、その深さを記憶します。 *
    2:RESERVES Array @@ -21,21 +21,22 @@ *
    dispatch 中に kill() が呼ばれた場合に一旦 kill をキャンセルし、完了時(DISPATCHING===0)に再度 kill() するためのフラグです。 *
    * - * @class __X_EventDispatcher_Listeners__ + * @class __Listeners__ * @private * @abstract */ -var - /** @enum {number} */ - X_Listeners_ = - /** @lends __X_EventDispatcher_Listeners__ */ - { - ACTUAL_HANDLER : 0, - DISPATCHING : 1, - RESERVES : 2, - UNLISTENS : 3, - KILL_RESERVED : 4 // X.Event で、イベントIDを 5 から始めているので注意。 - }; +var X_Listeners_; + +var /** @const */ + X_LISTENERS_ACTUAL_HANDLER = 0, + /** @const */ + X_LISTENERS_DISPATCHING = 1, + /** @const */ + X_LISTENERS_RESERVES = 2, + /** @const */ + X_LISTENERS_UNLISTENS = 3, + /** @const */ + X_LISTENERS_KILL_RESERVED = 4; // X.Event で、イベントIDを 5 から始めているので注意。 @@ -200,7 +201,7 @@ var X_EventDispatcher = X[ 'EventDispatcher' ] = cbHash = X_Callback_classifyCallbackArgs( opt_arg1, opt_arg2, opt_arg3, this ); }; - if( ( unlistens = listeners[ X_Listeners_.UNLISTENS ] ) && ( unlistens = unlistens[ opt_type ] ) ){ + if( ( unlistens = listeners[ X_LISTENERS_UNLISTENS ] ) && ( unlistens = unlistens[ opt_type ] ) ){ for( i = unlistens.length; i; ){ f = unlistens[ --i ]; if( f === cbHash || ( f.context === cbHash.context && f.func === cbHash.func && f.supplement === cbHash.supplement && f.lock === lock ) ) return false; @@ -267,21 +268,21 @@ function X_EventDispatcher_dispatch( e ){ e[ 'target' ] = e[ 'target' ] || this; e[ 'currentTarget' ] = e[ 'currentTarget' ] || this; - if( listeners[ X_Listeners_.DISPATCHING ] ){ - ++listeners[ X_Listeners_.DISPATCHING ]; + if( listeners[ X_LISTENERS_DISPATCHING ] ){ + ++listeners[ X_LISTENERS_DISPATCHING ]; } else { - listeners[ X_Listeners_.DISPATCHING ] = 1; + listeners[ X_LISTENERS_DISPATCHING ] = 1; }; // todo: // type も保存 - listeners[ X_Listeners_.UNLISTENS ] = listeners[ X_Listeners_.UNLISTENS ] || {}; - unlistens = listeners[ X_Listeners_.UNLISTENS ][ type ]; + listeners[ X_LISTENERS_UNLISTENS ] = listeners[ X_LISTENERS_UNLISTENS ] || {}; + unlistens = listeners[ X_LISTENERS_UNLISTENS ][ type ]; for( i = 0; i < list.length; ++i ){ f = list[ i ]; if( !unlistens ){ - unlistens = listeners[ X_Listeners_.UNLISTENS ][ type ]; + unlistens = listeners[ X_LISTENERS_UNLISTENS ][ type ]; }; if( unlistens && unlistens.indexOf( f ) !== -1 ) continue; @@ -290,25 +291,25 @@ function X_EventDispatcher_dispatch( e ){ if( f.once || r & X_Callback_UN_LISTEN ){ // dispatch 中に unlisten が作られることがある if( !unlistens ){ - unlistens = listeners[ X_Listeners_.UNLISTENS ] || ( listeners[ X_Listeners_.UNLISTENS ] = {} ); + unlistens = listeners[ X_LISTENERS_UNLISTENS ] || ( listeners[ X_LISTENERS_UNLISTENS ] = {} ); unlistens = unlistens[ type ] || ( unlistens[ type ] = [] ); }; unlistens.indexOf( f ) === -1 && ( unlistens[ unlistens.length ] = f ); }; ret |= X_Type_isFinite( r ) ? r : 0; - if( r & X_Callback_STOP_NOW ){ + if( r & X_Callback_STOP_NOW === X_Callback_STOP_NOW ){ sysOnly = true; break; }; }; - if( ( --listeners[ X_Listeners_.DISPATCHING ] ) === 0 ){ + if( ( --listeners[ X_LISTENERS_DISPATCHING ] ) === 0 ){ - delete listeners[ X_Listeners_.DISPATCHING ]; + delete listeners[ X_LISTENERS_DISPATCHING ]; // dispatch 中に listen されたイベントの追加 - if( list = listeners[ X_Listeners_.RESERVES ] ){ + if( list = listeners[ X_LISTENERS_RESERVES ] ){ for( i = 0, l = list.length; i < l; ++i ){ f = list[ i ]; X_EventDispatcher_once = f[ 4 ]; @@ -318,12 +319,12 @@ function X_EventDispatcher_dispatch( e ){ }; list.length = 0; X_EventDispatcher_once = X_EventDispatcher_lock = false; - delete listeners[ X_Listeners_.RESERVES ]; + delete listeners[ X_LISTENERS_RESERVES ]; }; // dispatch 中に unlisten されたイベントの削除 - if( unlistens = listeners[ X_Listeners_.UNLISTENS ] ){ - delete listeners[ X_Listeners_.UNLISTENS ]; + if( unlistens = listeners[ X_LISTENERS_UNLISTENS ] ){ + delete listeners[ X_LISTENERS_UNLISTENS ]; // _unlistens に入っている callbackHash は、lock をクリアしている X_EventDispatcher_unlock = true; @@ -343,7 +344,7 @@ function X_EventDispatcher_dispatch( e ){ delete X_EventDispatcher_LAZY_TIMERS[ X_Timer_currentUID ]; }; - if( listeners[ X_Listeners_.KILL_RESERVED ] ){ + if( listeners[ X_LISTENERS_KILL_RESERVED ] ){ /* for( timerID in X_EventDispatcher_LAZY_TIMERS ){ if( X_EventDispatcher_LAZY_TIMERS[ timerID ] === this ) return ret; @@ -393,9 +394,9 @@ function X_EventDispatcher_listen( type, opt_arg1, opt_arg2, opt_arg3 ){ if( !type ) return this; - if( listeners && listeners[ X_Listeners_.DISPATCHING ] ){ - if( !listeners[ X_Listeners_.RESERVES ] ) listeners[ X_Listeners_.RESERVES ] = []; - listeners[ X_Listeners_.RESERVES ][ listeners[ X_Listeners_.RESERVES ].length ] = [ type, opt_arg1, opt_arg2, opt_arg3, X_EventDispatcher_once, X_EventDispatcher_lock ]; + if( listeners && listeners[ X_LISTENERS_DISPATCHING ] ){ + if( !listeners[ X_LISTENERS_RESERVES ] ) listeners[ X_LISTENERS_RESERVES ] = []; + listeners[ X_LISTENERS_RESERVES ][ listeners[ X_LISTENERS_RESERVES ].length ] = [ type, opt_arg1, opt_arg2, opt_arg3, X_EventDispatcher_once, X_EventDispatcher_lock ]; return this; }; @@ -457,12 +458,12 @@ function X_EventDispatcher_unlisten( opt_type, opt_arg1, opt_arg2, opt_arg3 ){ return this; }; - if( reserves = listeners[ X_Listeners_.RESERVES ] ){ + if( reserves = listeners[ X_LISTENERS_RESERVES ] ){ for( i = reserves.length; i; ){ f = reserves[ --i ]; if( f[ 0 ] === opt_type && f[ 1 ] === opt_arg1 && f[ 2 ] === opt_arg2 && f[ 3 ] === opt_arg3 && ( !f[ 5 ] || X_EventDispatcher_unlock ) ){ reserves.splice( i, 1 ); - if( !reserves.legth ) delete listeners[ X_Listeners_.RESERVES ]; + if( !reserves.legth ) delete listeners[ X_LISTENERS_RESERVES ]; return this; }; }; @@ -475,11 +476,11 @@ function X_EventDispatcher_unlisten( opt_type, opt_arg1, opt_arg2, opt_arg3 ){ f = ( list = listeners[ opt_type ] )[ i ]; - if( unlistens = listeners[ X_Listeners_.UNLISTENS ] ){ + if( unlistens = listeners[ X_LISTENERS_UNLISTENS ] ){ // _unlistens に入っている callbackHash は、lock のチェックは済んでいる ( unlistens = unlistens[ opt_type ] ) ? ( unlistens[ unlistens.length ] = f ) : - ( listeners[ X_Listeners_.UNLISTENS ][ opt_type ] = [ f ] ); + ( listeners[ X_LISTENERS_UNLISTENS ][ opt_type ] = [ f ] ); } else { delete f.once; list.splice( i, 1 ); @@ -490,7 +491,7 @@ function X_EventDispatcher_unlisten( opt_type, opt_arg1, opt_arg2, opt_arg3 ){ // TODO カウンター empty = true; for( k in listeners ){ - if( k <= X_Listeners_.KILL_RESERVED ) continue; + if( k <= X_LISTENERS_KILL_RESERVED ) continue; empty = false; break; }; @@ -519,7 +520,7 @@ function X_EventDispatcher_unlistenAll( that ){ for( type in listeners ){ //if( X_EMPTY_OBJECT[ opt_type ] ) continue; - if( type <= X_Listeners_.KILL_RESERVED ) continue; + if( type <= X_LISTENERS_KILL_RESERVED ) continue; list = listeners[ type ]; for( i = list.length; i; ){ that[ 'unlisten' ]( type, list[ --i ] ); @@ -528,7 +529,7 @@ function X_EventDispatcher_unlistenAll( that ){ }; function X_EventDispatcher_addEvent( that, type, raw, list ){ - var i; + var i, f; X_EventDispatcher_lock || ( type = X_Event_Rename[ type ] || type ); if( X_Type_isArray( type ) ){ @@ -537,21 +538,16 @@ function X_EventDispatcher_addEvent( that, type, raw, list ){ console.log( 'events fix > ' + type[ i ] ); }; } else { - X_EventDispatcher_actualAddEvent( that, type, raw, list ); - }; -}; - -var X_EventDispatcher_actualAddEvent = + // Days on the Moon DOM Events とブラウザの実装 // http://nanto.asablo.jp/blog/2007/03/23/1339502 // Safari 2 では関数オブジェクトしか EventListener として使えませんが、Safari のナイトリービルドでは handleEvent メソッドを持つオブジェクトも EventListener として使えるようです。 - X_UA_EVENT.W3C ? - (function( that, type, raw, list ){ - var f; + + if( X_UA_EVENT.W3C ){ switch( that[ '_rawType' ] ){ case X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT : list.slcallback = X_Callback_create( that, X_EventDispatcher_sliverLightDispatch, [ type ] ); - list.sltoken = raw.AddEventListener( type, list.slcallback ); + list.sltoken = raw[ 'AddEventListener' ]( type, list.slcallback ); break; case X_EventDispatcher_EVENT_TARGET_XHR : @@ -571,7 +567,7 @@ var X_EventDispatcher_actualAddEvent = type === 'animationiteration' || type === 'webkitAnimationIteration' ) ){ raw.addEventListener( type, X_EventDispatcher_iOSTransitionEndDispatch, false ); } else { - f = that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] || ( that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) ); + f = that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] || ( that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) ); if( raw.addEventListener ){ raw.addEventListener( type, f, false ); @@ -581,14 +577,12 @@ var X_EventDispatcher_actualAddEvent = }; }; }; - }) : - X_UA_EVENT.IE ? - (function( that, type, raw, list ){ - var f; + } else + if( X_UA_EVENT.IE ){ switch( that[ '_rawType' ] ){ case X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT : list.slcallback = X_Callback_create( that, X_EventDispatcher_sliverLightDispatch, [ type ] ); - list.sltoken = raw.AddEventListener( type, list.slcallback ); + list.sltoken = raw[ 'AddEventListener' ]( type, list.slcallback ); break; case X_EventDispatcher_EVENT_TARGET_XHR : @@ -598,7 +592,7 @@ var X_EventDispatcher_actualAddEvent = break; default : - f = that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] || ( that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) ); + f = that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] || ( that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) ); if( raw.attachEvent ){ raw.attachEvent( 'on' + type, f ); @@ -607,13 +601,12 @@ var X_EventDispatcher_actualAddEvent = }; break; }; - }) : - (function( that, type, raw, list ){ + } else { switch( that[ '_rawType' ] ){ case X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT : // DOM0 で Silverlight ってあるの -> ie4 mobile? list.slcallback = X_Callback_create( that, X_EventDispatcher_sliverLightDispatch, [ type ] ); - list.sltoken = raw.AddEventListener( type, list.slcallback ); + list.sltoken = raw[ 'AddEventListener' ]( type, list.slcallback ); break; case X_EventDispatcher_EVENT_TARGET_XHR : @@ -622,14 +615,17 @@ var X_EventDispatcher_actualAddEvent = break; default : - raw[ 'on' + type ] = that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] || ( that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) ); + raw[ 'on' + type ] = that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] || ( that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] = X_Callback_create( that, X_EventDispatcher_actualHandleEvent ) ); break; }; - }); + } + }; +}; + /* * iOS の webkitTransitionEnd が連続して起こる場合、 - * コールバックの(that[ X_Listeners_.ACTUAL_HANDLER ])クロージャ内の実際のコールバック(X_Callback_actualClosure:obj._)が + * コールバックの(that[ X_LISTENERS_ACTUAL_HANDLER ])クロージャ内の実際のコールバック(X_Callback_actualClosure:obj._)が * 参照できていない問題に遭遇、、、iOS3.1.3 & iOS6.1.5 で確認 * animation も怪しい、、、 */ @@ -654,16 +650,10 @@ function X_EventDispatcher_removeEvent( that, type, raw, list, skip ){ X_EventDispatcher_systemUnlisten( that, type[ --i ], X_emptyFunction ); }; } else { - X_EventDispatcher_actualRemoveEvent( that, type, raw, list, skip ); - }; -}; - -var X_EventDispatcher_actualRemoveEvent = - X_UA_EVENT.W3C ? - (function( that, type, raw, list, skip ){ + if( X_UA_EVENT.W3C ){ switch( that[ '_rawType' ] ){ case X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT : - raw.RemoveEventListener( type, list.sltoken ); // token + raw[ 'RemoveEventListener' ]( type, list.sltoken ); // token X_Callback_correct( list.slcallback ); delete list.sltoken; delete list.slcallback; @@ -686,22 +676,21 @@ var X_EventDispatcher_actualRemoveEvent = raw.removeEventListener( type, X_EventDispatcher_iOSTransitionEndDispatch, false ); } else if( raw.addEventListener ){ - raw.removeEventListener( type, that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ], false ); + raw.removeEventListener( type, that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ], false ); } else { raw[ 'on' + type ] = null; }; - if( !skip && that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] ){ - X_Callback_correct( that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] ); - delete that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ]; + if( !skip && that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] ){ + X_Callback_correct( that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] ); + delete that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ]; }; }; - }) : - X_UA_EVENT.IE ? - (function( that, type, raw, list, skip ){ + } else + if( X_UA_EVENT.IE ){ switch( that[ '_rawType' ] ){ case X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT : - raw.RemoveEventListener( type, list.sltoken ); // token + raw[ 'RemoveEventListener' ]( type, list.sltoken ); // token X_Callback_correct( list.slcallback ); delete list.sltoken; delete list.slcallback; @@ -716,22 +705,21 @@ var X_EventDispatcher_actualRemoveEvent = default : if( raw.attachEvent ){ - raw.detachEvent( 'on' + type, that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] ); + raw.detachEvent( 'on' + type, that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] ); } else { raw[ 'on' + type ] = X_emptyFunction; raw[ 'on' + type ] = ''; }; if( !skip ){ - X_Callback_correct( that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] ); - delete that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ]; + X_Callback_correct( that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] ); + delete that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ]; }; }; - }) : - (function( that, type, raw, list, skip ){ + } else { switch( that[ '_rawType' ] ){ case X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT : - raw.RemoveEventListener( type, list.sltoken ); // token + raw[ 'RemoveEventListener' ]( type, list.sltoken ); // token X_Callback_correct( list.slcallback ); delete list.sltoken; delete list.slcallback; @@ -748,11 +736,13 @@ var X_EventDispatcher_actualRemoveEvent = raw[ 'on' + type ] = ''; if( !skip ){ - X_Callback_correct( that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ] ); - delete that[ '_listeners' ][ X_Listeners_.ACTUAL_HANDLER ]; + X_Callback_correct( that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ] ); + delete that[ '_listeners' ][ X_LISTENERS_ACTUAL_HANDLER ]; }; }; - }); + }; + }; +}; // TODO ブラウザからの呼び出しの最後に登録された関数を呼び出す機能(例えば画面の更新) @@ -832,7 +822,7 @@ function X_EventDispatcher_toggleAllEvents( that, add ){ if( !list || !raw ) return; for( type in list ){ //if( X_EMPTY_OBJECT[ type ] ) continue; - //if( type <= X_Listeners_.KILL_RESERVED ) continue; + //if( type <= X_LISTENERS_KILL_RESERVED ) continue; // 数字イベントの除外 if( !X_String_isNumberString( type ) ){ // TODO type rename はここ diff --git a/0.6.x/js/01_core/16_XViewPort.js b/0.6.x/js/01_core/16_XViewPort.js index d2540f4..5a4ad5f 100644 --- a/0.6.x/js/01_core/16_XViewPort.js +++ b/0.6.x/js/01_core/16_XViewPort.js @@ -46,6 +46,8 @@ X_ViewPort = X_Class_override( return X_ViewPort[ 'dispatch' ]( X_EVENT_BEFORE_UNLOAD ); case 'unload' : + //https://developer.mozilla.org/ja/docs/Web/JavaScript/A_re-introduction_to_JavaScript + //Firefox 1.5 の bfcache が無効になりますので、他に理由がない限り Firefox では unload リスナを登録するべきではないことに注意してください。 X_ViewPort[ 'dispatch' ]( X_EVENT_UNLOAD ); //alert('unload'); X_ViewPort_document[ 'kill' ](); @@ -304,12 +306,27 @@ X[ 'ViewPort' ] = { X_ViewPort_rootElement = document.compatMode !== 'CSS1Compat' ? elmBody : elmHtml || elmBody; - html = X[ 'Doc' ][ 'html' ] = X_Node_html = elmHtml && new Node( elmHtml )[ 'removeClass' ]( 'js-disabled' )[ 'addClass' ]( X_UA_classNameForHTML ); + /** + * Node( documentElement ) + * @alias X.Doc.html + * @type {Node} + */ + X[ 'Doc' ][ 'html' ] = html = X_Node_html = elmHtml && new Node( elmHtml )[ 'removeClass' ]( 'js-disabled' )[ 'addClass' ]( X_UA_classNameForHTML ); html[ '_flags' ] |= X_Node_State.IN_TREE; - - head = X[ 'Doc' ][ 'head' ] = X_Node_head = elmHead && new Node( elmHead ); - - body = X[ 'Doc' ][ 'body' ] = X_Node_body = new Node( elmBody ); + + /** + * Node( head ) + * @alias X.Doc.head + * @type {Node} + */ + X[ 'Doc' ][ 'head' ] = head = X_Node_head = elmHead && new Node( elmHead ); + + /** + * Node( documentElement ) + * @alias X.Doc.body + * @type {Node} + */ + X[ 'Doc' ][ 'body' ] = body = X_Node_body = new Node( elmBody ); body[ 'parent ' ] = head[ 'parent' ] = html; html[ '_xnodes' ] = [ head, body ]; diff --git a/0.6.x/js/02_dom/00_XDoc.js b/0.6.x/js/02_dom/00_XDoc.js index 48251a6..457bdc1 100644 --- a/0.6.x/js/02_dom/00_XDoc.js +++ b/0.6.x/js/02_dom/00_XDoc.js @@ -4,6 +4,10 @@ * @alias X.Doc */ X[ 'Doc' ] = { + /** + * EventDispatcher.prototype.listen 参照 + * @alias X.Doc.listen + */ 'listen' : function( type, arg1, arg2, arg3 ){ if( type <= X_ViewPort_readyState && type === 'DOMContentLoaded' ){ /* @@ -15,7 +19,10 @@ X[ 'Doc' ] = { return X[ 'Doc' ]; }, - + /** + * EventDispatcher.prototype.listenOnce 参照 + * @alias X.Doc.listenOnce + */ 'listenOnce' : function( type, arg1, arg2, arg3 ){ if( type <= X_ViewPort_readyState && type === 'DOMContentLoaded' ){ /* @@ -26,12 +33,20 @@ X[ 'Doc' ] = { type && arg1 && X_ViewPort_document[ 'listenOnce' ]( type, arg1, arg2, arg3 ); return X[ 'Doc' ]; }, - + + /** + * EventDispatcher.prototype.unlisten 参照 + * @alias X.Doc.unlisten + */ 'unlisten' : function( type, arg1, arg2, arg3 ){ type && arg1 && X_ViewPort_document[ 'unlisten' ]( type, arg1, arg2, arg3 ); return X[ 'Doc' ]; }, - + + /** + * EventDispatcher.prototype.listening 参照 + * @alias X.Doc.listening + */ 'listening' : function( type, arg1, arg2, arg3 ){ return X_ViewPort_document[ 'listening' ]( type, arg1, arg2, arg3 ); }, @@ -39,11 +54,7 @@ X[ 'Doc' ] = { 'create' : X_Doc_create, 'createText' : X_Doc_createText - - // html - // head - // body - // find + }; /** diff --git a/0.6.x/js/02_dom/05_XNodeAttr.js b/0.6.x/js/02_dom/05_XNodeAttr.js index 3f9a059..9a33207 100644 --- a/0.6.x/js/02_dom/05_XNodeAttr.js +++ b/0.6.x/js/02_dom/05_XNodeAttr.js @@ -148,13 +148,18 @@ function X_Node_Attr_objToAttrText( that, skipNetworkForElmCreation ){ })( X_Node_Attr_renameForDOM, X_Node_Attr_renameForTag ); - -/* -------------------------------------- - * attribute - * - * http://nanto.asablo.jp/blog/2005/10/29/123294 - * className, onclick等 はここで設定しない - * +/** + * 属性の getter と setter。onclick等はできないので listen, listenOnce を使うこと。http://nanto.asablo.jp/blog/2005/10/29/123294 + * @alias Node.prototype.attr + * @param {string|object} [nameOrObj] 属性名、または追加する属性のハッシュ + * @param {string|number} [value=] 属性の値 + * @return {Node|string|number} getter の場合は値を、setter の場合は自身を返す。(メソッドチェーン) + * @example // getter + * node.attr( 'tagName' ) === 'DIV'; + * // setter - 1 + * node.attr( { src : url, width : 100, height : 100 } ); + * // setter - 2 + * node.attr( 'src', url ); */ Node.prototype[ 'attr' ] = function( nameOrObj /* v */ ){ var attrs = this[ '_attrs' ], newAttrs, f, k, elm, v; diff --git a/0.6.x/js/02_dom/06_XNodeCSS.js b/0.6.x/js/02_dom/06_XNodeCSS.js index ce4a789..9a9d85b 100644 --- a/0.6.x/js/02_dom/06_XNodeCSS.js +++ b/0.6.x/js/02_dom/06_XNodeCSS.js @@ -504,8 +504,20 @@ function X_Node_CSS__splitValueAndUnit( v ){ // unitID, name 単位指定のプロパティ取得 geter // obj setter // name, value setter - -Node.prototype[ 'css' ] = function( nameOrObj /* orUnitID, valuOrUnitOrName */ ){ +/** + * style の getter と setter。 + * @alias Node.prototype.css + * @param {string|object} [nameOrObj] style 名、または追加する style のハッシュ + * @param {string|number} [value=] style の値 + * @return {Node|string|number} getter の場合は値を、setter の場合は自身を返す。(メソッドチェーン) + * @example // getter + * node.css( 'color' ); + * // setter - 1 + * node.css( { width : w + 'px', height : h + 'px' } ); + * // setter - 2 + * node.css( 'color', 0x666666 ); + */ +Node.prototype[ 'css' ] = function( nameOrObj /* value */ ){ var args = arguments, css = this[ '_css' ], p, name, v, plain, camelize, flags; @@ -543,7 +555,7 @@ Node.prototype[ 'css' ] = function( nameOrObj /* orUnitID, valuOrUnitOrName */ ) }; // getter if( !css ) return; - // 集計 border, padding, margin, backgroundPosition, clip + // TODO 集計 border, padding, margin, backgroundPosition, clip // TODO border で正確なデータを返せない時は、null を返す return css[ X_Node_CSS_camelize( nameOrObj ) ]; }; diff --git a/0.6.x/js/02_dom/08_XNodeSelector.js b/0.6.x/js/02_dom/08_XNodeSelector.js index a1b6fbd..00b0013 100644 --- a/0.6.x/js/02_dom/08_XNodeSelector.js +++ b/0.6.x/js/02_dom/08_XNodeSelector.js @@ -56,9 +56,6 @@ var // TODO { a : 1, A : 2, _ : 3,,, } X_Node_Selector__ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-0123456789\\', X_Node_Selector__NUMBER = '+-0123456789'; - -// XMLWrapper のために今だけ外部に公開 -X_NodeSelector_parse = X_Node_Selector__parse; /* * セレクタ文字列の解析、但し一挙に行わず、ひと塊づつ @@ -228,7 +225,13 @@ function X_Node_Selector__parse( query, last ){ return not ? [ i + tmp + 1, [ combinator, 6, result ] ] : [ i, result ]; }; - // セレクター + /** + * selector を使って Node を取得する + * @alias X.Doc.find + * @function + * @param {string} セレクター文字列 + * @return {Node|NodeList} + */ X[ 'Doc' ][ 'find' ] = X_shortcutFunction = Node.prototype[ 'find' ] = X_NodeList.prototype[ 'find' ] = function ( queryString ){ var HTML = X_Node_html, scope = this.constructor === X_NodeList && this.length ? this : [ this.constructor === Node ? this : X_Node_body ], diff --git a/0.6.x/js/02_dom/10_XNodeAnime.js b/0.6.x/js/02_dom/10_XNodeAnime.js index 0b5a8a8..3913195 100644 --- a/0.6.x/js/02_dom/10_XNodeAnime.js +++ b/0.6.x/js/02_dom/10_XNodeAnime.js @@ -1,26 +1,26 @@ var ease = { - quadratic: { + 'quadratic' : { style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', fn: function (k) { return k * ( 2 - k ); } }, - circular: { + 'circular' : { style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1) fn: function (k) { return Math.sqrt( 1 - ( --k * k ) ); } }, - back: { + 'back' : { style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)', fn: function (k) { var b = 4; return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1; } }, - bounce: { + 'bounce' : { style: '', fn: function (k) { if ( ( k /= 1 ) < ( 1 / 2.75 ) ) { @@ -34,7 +34,7 @@ var } } }, - elastic: { + 'elastic' : { style: '', fn: function (k) { var f = 0.22, @@ -79,6 +79,7 @@ var X_Node_ANIMATIONS = [], // 中断 /* + * TODO : DX Anime * TODO : scale, ActiveX transform, zoom, fontSizeScale * TODO : rotate, ActiveX transform -> 位置補正のために size 情報が必要なので、commitUpdate 後に計算して適用 * TODO : matrix @@ -92,20 +93,20 @@ Node.prototype[ 'animate' ] = function( start, dest, duration, easing, wait ){ obj = this[ '_anime' ] || ( this[ '_anime' ] = {} ); obj.duration = 0 <= duration && X_Type_isFinite( duration ) ? duration : 500; - obj.easing = ease[ easing ] || ease.circular; + obj.easing = ease[ easing ] || ease[ 'circular' ]; // 現在 GPUレイヤーのtop になっているか?将来については phase で判定 obj.gpuParent = obj.gpuParent || false; obj.phase = duration === 0 ? 9 : obj.phase === 9 ? 9 : 0; // obj.wait = X_Type_isFinite( wait ) ? wait : 1000; obj.startTime = X_Timer_now(); - obj.startX = ( start.x || start.x === 0 ) ? start.x : obj.x || 0; - obj.startY = ( start.y || start.y === 0 ) ? start.y : obj.y || 0; + obj.startX = ( start.x || start.x === 0 ) ? start.x : obj.x || NaN; + obj.startY = ( start.y || start.y === 0 ) ? start.y : obj.y || NaN; obj.startA = 0 <= start.opacity && start.opacity <= 1 ? start.opacity : obj.a || 1; obj.destTime = obj.startTime + obj.duration; - obj.destX = ( dest.x || dest.x === 0 ) ? dest.x : obj.destX || 0; - obj.destY = ( dest.y || dest.y === 0 ) ? dest.y : obj.destY || 0; + obj.destX = ( dest.x || dest.x === 0 ) ? dest.x : obj.destX || NaN; + obj.destY = ( dest.y || dest.y === 0 ) ? dest.y : obj.destY || NaN; obj.destA = 0 <= dest.opacity && dest.opacity <= 1 ? dest.opacity : obj.destA || 1; X_Node_ANIMATIONS.indexOf( this ) === -1 && @@ -489,14 +490,15 @@ function X_Node_Anime_updatePosition( xnode, x, y, opacity, useGPU ){ if( X_Node_Anime_hasTransform ){ xnode[ 'css' ]({ transform : 'translate(' + ( x | 0 ) + 'px,' + ( y | 0 ) + 'px)' + ( useGPU ? X_Node_Anime_translateZ : '' ), - opacity : opacity + opacity : opacity === 1 ? '' : opacity }); } else { - xnode[ 'css' ]({ + x === x && xnode[ 'css' ]({ left : ( x | 0 ) + 'px', + opacity : opacity === 1 ? '' : opacity }); + y === y && xnode[ 'css' ]({ top : ( y | 0 ) + 'px', - opacity : opacity - }); + opacity : opacity === 1 ? '' : opacity }); }; if( X_Node_Anime_translateZ ){ diff --git a/0.6.x/js/03_plugin/00_XPlugin.js b/0.6.x/js/03_plugin/00_XPlugin.js index be90379..a7858e4 100644 --- a/0.6.x/js/03_plugin/00_XPlugin.js +++ b/0.6.x/js/03_plugin/00_XPlugin.js @@ -21,7 +21,7 @@ var X_Pulgin_FLASH_VERSION = parseFloat( navigator.plugins[ 'Shockwave Flash' ].version ) : !X_UA[ 'IE4' ] && !X_UA[ 'IE5' ] && X_UA[ 'ActiveX' ] ? (function(){ var obj = eval( 'var a,e;try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash")}catch(e){}a' ), - ver = obj && obj.GetVariable( '$version' ).split( ' ' ).join( '.' ); + ver = obj && obj[ 'GetVariable' ]( '$version' ).split( ' ' ).join( '.' ); return parseFloat( ver ) || 0; })() : 0, @@ -38,7 +38,7 @@ var X_Pulgin_FLASH_VERSION = X_Pulgin_SILVER_LIGHT_VERSION = !X_UA[ 'IE' ] && navigator.plugins[ 'Silverlight Plug-In' ] ? parseFloat( navigator.plugins[ 'Silverlight Plug-In' ].version ) : - X_UA[ 'ActiveX' ] && 6 <= X_UA[ 'IE' ] /* && !X_UA[ 'IECompat' ] */ ? (function(){ + X_UA[ 'ActiveX' ] && 6 <= X_UA[ 'IE' ] ? (function(){ return eval( 'var a,i=0;try{a=new ActiveXObject("AgControl.AgControl");for(i=5;i;--i)if(a.IsVersionSupported(i+".0"))break;}catch(e){i=0}i' ); })() : 0, diff --git a/0.6.x/js/05_util/04_XXML.js b/0.6.x/js/05_util/04_XXML.js new file mode 100644 index 0000000..696606c --- /dev/null +++ b/0.6.x/js/05_util/04_XXML.js @@ -0,0 +1,575 @@ +/* + * XMLWrapper_find 周りの オリジナルコードに関する情報 + * Original code by pettanR team + * - http://sourceforge.jp/projects/pettanr/scm/git/clientJs/blobs/master/0.6.x/js/01_dom/18_XDomQuery.js + * and + * Original code by ofk ( kQuery, ksk ) + * - http://d.hatena.ne.jp/ofk/comment/20090106/1231258010 + * - http://d.hatena.ne.jp/ofk/20090111/1231668170 + */ + +X[ 'XML' ] = XMLWrapper; + +function XMLWrapper( xml ){ + this._rawXML = xml; +}; + +XMLWrapper.prototype.length = 1; +XMLWrapper.prototype.has = XMLWrapper_has; +XMLWrapper.prototype.get = XMLWrapper_get; +XMLWrapper.prototype.val = XMLWrapper_val; +XMLWrapper.prototype.find = XMLWrapper_find; + +function XMLWrapper_has( queryString ){ + return !!this.find( queryString ).length; +}; + +function XMLWrapper_get( index ){ + if( this.length === 1 ) return this; + if( this.length === 0 ) return null; + // 一度発行した XMLWrapper は控えて置いて再発行する。 + if( this._wraps && this._wraps[ index ] ) return this._wraps[ index ]; + if( !this._wraps ) this._wraps = []; + return this[ index ] ? + ( this._wraps[ index ] = new XMLWrapper( this[ index ] ) ) : + null; +}; + +function XMLWrapper_val( queryString, type ){ + var attr_textContent = X.UA.IE < 9 || X.UA.Opera ? 'innerText' : X.UA.IE9 ? 'text' : 'textContent', + wrapper, xml, v; + + switch( queryString ){ + case 'number' : + case 'int' : + case 'boolean' : + case 'string' : + case undefined : + type = queryString; + queryString = undefined; + }; + + wrapper = queryString ? this.find( queryString ) : this; + xml = wrapper.length === 1 ? wrapper._rawXML : wrapper[ 0 ]; + + if( !xml ){ + switch( type ){ + case 'number' : + case 'int' : + return NaN; + case 'boolean' : + return false; + case 'string' : + return ''; + default : + return null; + }; + }; + + v = xml.nodeType === 1 ? xml.innerText || xml.text || xml.textContent : xml.nodeValue; + //xml.toStrign() + switch( type ){ + case 'number' : + return parseFloat( v ); + case 'int' : + return parseInt( v ); + case 'boolean' : + return v && v !== '0' && v !== 'false' && v !== 'null' && v !== 'undefined' && v !== 'NaN'; + //case 'string' : + //default : + }; + return v || ''; +}; + + function XMLWrapper_find( queryString ){ + + var scope = this.constructor === XMLListWrapper ? this : [ this._rawXML ], + parents = scope, // 探索元の親要素 xmlList の場合あり + ARY_PUSH = Array.prototype.push, + ret = [], // 結果要素 + isXML = true, + isMulti = 1 < scope.length,// 要素をマージする必要がある + isStart = true, + _ = ' ', + isAll, isNot, hasRoot, + l, i, n, parsed, + xmlList, // 一時保存用 + merge, // 要素がコメントノードで汚染されている場合使う + combinator, selector, name, tagName, + uid, tmp, xml, filter, key, op, val, toLower, useName, + links, className, attr, flag; + + // 文字列以外は空で返す + if( !X_Type_isString( queryString ) ) return XMLListWrapper_0; + + xmlList = []; + + // 以下、パースと探索 + for( ; queryString.length; ){ + //console.log( 'queryString[' + queryString + ']' ); + + // 初期化処理 + if( !parsed ){ + parsed = X_Node_Selector__parse( queryString ); + + if( X_Type_isNumber( parsed ) ){ + // error + return XMLListWrapper_0; + }; + + queryString = queryString.substr( parsed[ 0 ] ); + parsed = parsed[ 1 ]; + + if( parsed === 5 ){ + isMulti = true; + parents = scope; + xmlList && xmlList.length && ARY_PUSH.apply( ret, xmlList ); + parsed = null; + xmlList = []; + isStart = true; + continue; + }; + }; + + combinator = parsed[ 0 ]; + selector = parsed[ 1 ]; + name = parsed[ 2 ]; + tagName = selector === 1 ? name : '*'; + isAll = tagName === '*'; + + if( !isStart ){ + if( !xmlList.length ){ + parsed = null; + continue; + } else + if( combinator !== 0 ){ + parents = xmlList; + xmlList = []; + //console.log( 'cobinator !== 0 ' + parents.length + ' : ' + xmlList.length ); + }; + }; + + i = 0; + l = parents.length; + n = -1; + isMulti = isMulti || 1 < l; + + console.log( 'combinator ' + combinator ); + + switch( combinator ){ + // > TagName|* + case 2 : + for( ; i < l; ++i ){ + for( xml = parents[ i ].firstChild; xml; xml = xml.nextSibling ){ + if( xml.nodeType === 1 && ( isAll || tagName === xml.tagName ) ) xmlList[ ++n ] = xml; + }; + }; + break; + // + TagName|* + case 3 : + for( ; i < l; ++i ){ + for( xml = parents[ i ].nextSibling; xml; xml = xml.nextSibling ){ + if( xml.nodeType === 1 ){ + if( isAll || tagName === xml.tagName ) xmlList[ ++n ] = xml; + break; + }; + }; + }; + break; + // ~ TagName|* + case 4 : + merge = []; + for( ; i < l; ++i ){ + for( xml = parents[ i ].nextSibling; xml; xml = xml.nextSibling ){ + if( xml.nodeType === 1 && ( isAll || tagName === xml.tagName ) ){ + if( merge.indexOf( xml ) !== -1 ){ + break; + } else { + merge[ merge.length ] = xml; + xmlList[ ++n ] = xml; + }; + }; + }; + }; + break; + + // @ 属性ノード + case 6 : + selector = 0; + tagName = '*'; + for( ; i < l; ++i ){ + if( xml = parents[ i ].getAttributeNode( name ) ){ + xmlList[ ++n ] = xml; + }; + }; + break; + default : + if( combinator === 1 || ( isStart && selector < 7 ) ){ + console.log( l + ' > ' + xmlList.length + ' tag:' + tagName ); + for( ; i < l; ++i ){ + xml = parents[ i ]; + xml.childNodes && xml.childNodes.length && XMLWrapper_fetchElements( xmlList, xml, isAll ? null : tagName ); + }; + console.log( l + ' >> ' + xmlList.length + ' tag:' + tagName ); + }; + }; + + isStart = false; + + //alert( 'pre-selector:' + ( xmlList && xmlList.length ) ) + + switch( selector ){ + // #, ID + case 2 : + filter = [ 'id', 1, name ]; break; + // ., class + case 3 : + filter = [ 'class', 3 /*'~='*/, name ]; break; + // :, 擬似クラス + case 4 : + if( !( filter = XMLWrapper_filter[ name ] ) ){ + return XMLListWrapper_0;; + }; + break; + // [] 属性 + case 5 : + filter = [ name, parsed[ 3 ], parsed[ 4 ] ]; break; + // :not + case 6 : + isNot = true; + parsed = parsed[ 2 ]; + name = parsed[ 2 ]; + switch( parsed[ 1 ] ) { + case 1 : + filter = [ 'tag', 1, name ]; break; + // #, ID + case 2 : + filter = [ 'id', 1, name ]; break; + // ., class + case 3 : + filter = [ 'class', 3, name ]; break; + // :, 擬似クラス + case 4 : + if( !( filter = X_Node_Selector__filter[ name ] ) ){ + return []; + }; + break; + // [] 属性 + case 5 : + filter = [ name, parsed[ 3 ], parsed[ 4 ] ]; break; + }; + break; + // scope + case 7 : + xmlList = scope; break; + /* root + case 8 : + hasRoot = true; + xmlList = [ HTML ]; break; + // link + case 9 : + if( links = document.links ){ + for( xmlList = [], i = links.length; i; ){ + xmlList[ --i ] = new Node( links[ i ] ); + }; + } else { + // area[href],a[href] + }; */ + }; + + if( filter && xmlList.length ){ + // filter.mが関数の場合 + if( filter.m ){ + xmlList = filter.m( + { + not : isNot, + xml : isXML + }, + xmlList, + parsed[ 3 ], parsed[ 4 ] + ); + } else + // filterが関数の場合 + if( X_Type_isFunction( filter ) ){ + tmp = []; + for( i = 0, n = -1; xml = xmlList[ i ]; ++i ){ + if( ( !!filter( xml ) ) ^ isNot ) tmp[ ++n ] = xml; + }; + xmlList = tmp; + } else { + // 属性セレクター + tmp = []; + key = filter[ 0 ]; + op = filter[ 1 ]; + val = filter[ 2 ]; + + // 通常 + if( op === 3 ) val = _ + val + _; + + for( i = 0, n = -1, l = xmlList.length; i < l; ++i ){ + xml = xmlList[ i ]; + attr = elem.getAttribute( key, 2 ); + flag = attr != null;// && ( !useName || attr !== '' ); + if( flag && op ){ + //if( toLower ) attr = attr.toLowerCase(); + + switch( op ){ + case 1: // = + flag = attr === val; + break; + case 2: // != + flag = attr !== val; + break; + case 3: // ~= + flag = ( _ + attr + _ ).indexOf( val ) !== -1; + break; + case 4: // ^= + flag = attr.indexOf( val ) === 0; + break; + case 5: // $= + flag = attr.lastIndexOf( val ) + val.length === attr.length; + break; + case 6: // *= + flag = attr.indexOf( val ) !== -1; + break; + case 7: // |= + flag = attr === val || attr.substring( 0, val.length + 1 ) === val + '-'; + break; + }; + }; + if( !!flag ^ isNot ) tmp[ ++n ] = xml; + //}; + }; + xmlList = tmp; + }; + }; + filter = null; + isNot = false; + parsed = null; + + //console.log( '//end :' + ( xmlList && xmlList.length ) ); + }; + //console.log( 'multi:' + ( xmlList && xmlList.length ) ); + + // tree 順に並び替え、同一要素の排除 + if( isMulti ){ + xmlList && xmlList.length && ARY_PUSH.apply( ret, xmlList ); + l = ret.length; + if( l === 0 ) return XMLListWrapper_0; + if( l === 1 ) return new XMLWrapper( ret[ 0 ] ); + + xmlList = []; + //merge = []; + for( i = 0, n = -1; i < l; ++i ){ + //alert( 'multi:' + i ) + xml = ret[ i ]; + if( xmlList.indexOf( xml ) === -1 ){ + //merge[ merge.length ] = xml; + xmlList[ ++n ] = xml; + }; + }; + XMLWrapper_sortElementOrder( ret = [], xmlList, this._rawXML.childNodes ); + + // @ + for( i = 0, l = xmlList.length; i < l; ++i ){ + if( ret.indexOf( xml = xmlList[ i ] ) === -1 ){ + ret[ ret.length ] = xml; + }; + }; + + xmlList = ret; + }; + + return xmlList.length === 1 ? new XMLWrapper( xmlList[ 0 ] ) : new XMLListWrapper( xmlList ); + }; + + function XMLWrapper_sortElementOrder( newList, list, xmlList ){ + var l = xmlList.length, + i = 0, + j, child, _xmlList; + for( ; i < l; ++i ){ + child = xmlList[ i ]; + //if( child.nodeType !== 1 ) continue; + //console.log( child.tagName ); + if( ( j = list.indexOf( child ) ) !== -1 ){ + newList[ newList.length ] = child; + list.splice( j, 1 ); + if( list.length === 1 ){ + newList[ newList.length ] = list[ 0 ]; + list.length = 0; + return true; + }; + if( list.length === 0 ) return true; + }; + if( ( _xmlList = child.childNodes ) && XMLWrapper_sortElementOrder( newList, list, _xmlList ) ){ + return true; + }; + }; + }; + + function XMLWrapper_fetchElements( list, parent, tag ){ + var xmlList = parent.childNodes, + l = xmlList.length, + i = 0, + child; + for( ; i < l; ++i ){ + child = xmlList[ i ]; + if( child.nodeType === 1 ){ + ( !tag || child.tagName === tag ) && ( list[ list.length ] = child ); + //console.log( parent.tagName + ' > ' + child.tagName + ' == ' + tag+ ' l:' + list.length ); + child.childNodes && child.childNodes.length && XMLWrapper_fetchElements( list, child, tag ); + }; + }; + }; + + function XMLWrapper_funcSelectorChild( type, flag_all, flags, xmlList ){ + var res = [], + flag_not = flags.not, + i = 0, n = -1, xnode, node, + tagName, tmp; + for( ; xnode = xmlList[ i ]; ++i ){ + tagName = flag_all || xnode.tagName; + tmp = null; + if( /* tmp === null && */ type <= 0 ){ + for( node = xnode.previousSibling; node; node = node.previousSibling ){ + if( node.nodeType === 1 && ( flag_all || tagName === node.tagName ) ){ + tmp = false; + break; + }; + }; + }; + if( tmp === null && 0 <= type ){ + for( node = xnode.nextSibling; node; node = node.nextSibling ){ + if( node.nodeType === 1 && ( flag_all || tagName === node.tagName ) ){ + tmp = false; + break; + }; + }; + }; + if( tmp === null ) tmp = true; + if( tmp ^ flag_not ) res[ ++n ] = xnode; + }; + return res; + }; + function XMLWrapper_funcSelectorNth( pointer, sibling, flag_all, flags, xmlList, a, b ){ + var res = [], + checked = {}, + flag_not = flags.not, + i = 0, n = -1, uid, + c, xnode, tmp, node, tagName; + for( ; xnode = xmlList[ i ]; ++i ){ + uid = xnode._uid; + tmp = checked[ uid ]; + if( tmp === void 0 ){ + for( c = 0, node = xnode.parentNode[ pointer ], tagName = flag_all || xnode.tagName; node; node = node[ sibling ] ){ + if( node.nodeType === 1 && ( flag_all || tagName === node.tagName ) ){ + ++c; + checked[ node._uid ] = a === 0 ? c === b : (c - b) % a === 0 && (c - b) / a >= 0; + }; + }; + tmp = checked[ uid ]; + }; + if( tmp ^ flag_not ) res[ ++n ] = xnode; + }; + return res; + }; + function XMLWrapper_funcSelectorProp( prop, flag, flags, xmlList ){ + var res = [], + flag_not = flag ? flags.not : !flags.not, + i = 0, n = -1, xnode; + for( ; xnode = xmlList[ i ]; ++i ){ + if( xnode.getAttributeNode( prop ) ^ flag_not ) res[ ++n ] = xnode; + }; + return res; + }; + +var XMLWrapper_filter = { + 'first-child' : { + m : function( flags, xmlList ){ return XMLWrapper_funcSelectorChild( -1, true, flags, xmlList ); } + }, + 'last-child' : { + m : function( flags, xmlList ){ return XMLWrapper_funcSelectorChild( 1, true, flags, xmlList ); } + }, + 'only-child' : { + m : function( flags, xmlList ){ return XMLWrapper_funcSelectorChild( 0, true, flags, xmlList ); } + }, + 'first-of-type' : { + m : function( flags, xmlList ){ return XMLWrapper_funcSelectorChild( -1, false, flags, xmlList ); } + }, + 'last-of-type' : { + m : function( flags, xmlList ){ return XMLWrapper_funcSelectorChild( 1, false, flags, xmlList ); } + }, + 'only-of-type' : { + m : function( flags, xmlList ){ return XMLWrapper_funcSelectorChild( 0, false, flags, xmlList ); } + }, + 'nth-child' : { + m : function( flags, xmlList, a, b ){ return XMLWrapper_funcSelectorNth( 'firstChild', 'nextSibling', true, flags, xmlList, a, b ); } + }, + 'nth-last-child' : { + m : function( flags, xmlList, a, b ){ return XMLWrapper_funcSelectorNth( 'lastChild', 'previousSibling', true, flags, xmlList, a, b ); } + }, + 'nth-of-type' : { + m : function( flags, xmlList, a, b ){ return XMLWrapper_funcSelectorNth( 'firstChild', 'nextSibling', false, flags, xmlList, a, b ); } + }, + 'nth-last-of-type' : { + m : function( flags, xmlList, a, b ){ return XMLWrapper_funcSelectorNth( 'lastChild', 'previousSibling', false, flags, xmlList, a, b ); } + }, + empty : { + m : function( flags, xmlList ){ + var res = [], + flag_not = flags.not, + i = 0, n = -1, xnode, tmp, node; + for( ; xnode = xmlList[i]; ++i ){ + tmp = true; + for( node = xnode.firstChild; node; node = node.nextSibling ){ + if( node.nodeType === 1 || ( node.nodeType === 3 && node._text ) ){ + tmp = false; + break; + }; + }; + if( tmp ^ flag_not ) res[ ++n ] = xnode; + }; + return res; + } + }, + contains : { + m : function( flags, xmlList, arg ){ + var res = [], + flag_not = flags.not, + i = 0, n = -1, xnode, text = '', + // kquery + attr_textContent = X_UA[ 'IE' ] < 9 || X_UA[ 'Opera' ] ? 'innerText' : X_UA[ 'IE9' ] ? 'text' : 'textContent'; + for( ; xnode = xmlList[ i ]; ++i ){ + switch( xnode.nodeType ){ + case 1 : + text = xml.nodeType === 1 ? xml.innerText || xml.text || xml.textContent : xml.nodeValue;// xnode[ attr_textContent ]; + break; + case 2 : + case 3 : + text = xnode.nodeValue; + break; + }; + console.log( text + ' ' + arg ); + if ( ( -1 < text.indexOf( arg ) ) ^ flag_not ) res[ ++n ] = xnode; + }; + return res; + } + } +}; + +function XMLListWrapper( xmlList ){ + var i = 0, l = xmlList ? xmlList.length : 0; + for( ; i < l; ++i ){ + this[ i ] = xmlList[ i ]; + }; + this.length = l; +}; + +var XMLListWrapper_0 = new XMLListWrapper(); + +XMLListWrapper.prototype.length = 0; +XMLListWrapper.prototype._wraps = null; +XMLListWrapper.prototype.has = XMLWrapper_has; +XMLListWrapper.prototype.get = XMLWrapper_get; +XMLListWrapper.prototype.val = XMLWrapper_val; +XMLListWrapper.prototype.find = XMLWrapper_find; diff --git a/0.6.x/js/06_net/00_XNet.js b/0.6.x/js/06_net/00_XNet.js index 709f177..a97d45e 100644 --- a/0.6.x/js/06_net/00_XNet.js +++ b/0.6.x/js/06_net/00_XNet.js @@ -7,9 +7,42 @@ /** - * 通信のキャンセル 通信中の場合は停止&破棄する kill(), busy() メソッドだけを持つ。X.Pair によって、プライベートメンバは隠蔽される。X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_ERROR, X_EVENT_TIMEOUT, X_EVENT_CANCELED - * 通信待ち中はキャンセル&破棄、通信中の場合は通信の中断&破棄を行う。 - * SUCCESS, ERROR, TIMEOUT イベント以降は kill() は無視される。X.Net インスタンスは COMPLETE で自動で破棄される。 + *

    busy() メソッドだけを持つ。通信用のプロパティは X.Pair によって隠蔽される。 + *

    通信のキャンセル

    + *

    kill() で通信待ち中はキャンセル&破棄、通信中の場合は通信の中断&破棄を行う。SUCCESS, ERROR, TIMEOUT イベント以降は kill() は無視される。 + *

    イベント

    + *
    + *
    X.Event.PROGRESS
    通信進行状況 + *
    X.Event.SUCCESS
    通信成功 + *
    X.Event.ERROR
    通信エラー + *
    X.Event.TIMEOUT
    通信タイムアウト + *
    X.Event.CANCELED
    通信のユーザー、プログラムによるキャンセル + *
    X.Event.COMPLETE
    通信完了。SUCCESS, ERROR, TIMEOUT, CANCELED 後に発生。 + *
    + *

    X.Net インスタンスは COMPLETE 後に自動で破棄される。 + *

    必須プロパティ

    + *
    + *
    url
    URL + *
    type
    'xhr', 'jsonp', 'image', 'img' + *
    xhr
    URL { url : 'hoge', type : 'xhr' } の省略形 + *
    jsonp
    URL { url : 'hoge', type : 'jsonp' } の省略形 + *
    image, img
    URL { url : 'hoge', type : 'image' } の省略形 + *
    + *

    XHR 用プロパティ

    + *
    + *
    method
    'GET', 'POST' 未指定かつ postdata を設定している場合、'POST' になる。 + *
    postdata
    string, object の場合は X.String.serialize される。 + *
    async
    boolean + *
    username
    BASIC 認証 + *
    password
    BASIC 認証 + *
    headers
    object xhr.setRequestHeader する値 + *
    timeout
    タイムアウト ms + *
    cache
    headers[ 'Pragma' ] = 'no-cache' 等を設定するか? + *
    dataType
    'text', 'json', 'xml', 'blob', 'arraybuffer' 等。xhr.responseType に指定する値 + *
    mimeType
    'text/xml', 'audio/mpeg' 等。xhr.overrideMimeType する値 + *
    auth
    X.OAuth2 インスタンス(OAuth2 サービスの定義) + *
    + * * @alias X.Net * @class 各種ネットワーク機能をラップしインターフェイスを共通化する。 * @constructs Net @@ -34,6 +67,10 @@ X[ 'Net' ] = X_EventDispatcher[ 'inherits' ]( 'X.Net', X_Class.NONE, { + 'netType' : '', + 'netName' : '', + 'netVersion' : 0, + 'Constructor' : function( urlOrObject, opt_options ){ var v, opt, url, type; @@ -231,8 +268,8 @@ function X_NET_shiftQueue(){ switch( X_NET_currentData.netType ){ case X_NET_TYPE_XHR : - // TODO xProtocol, method -> gadget.io.makeRequset, flash - + // TODO (xProtocol | method) & canUse -> gadget.io.makeRequset, flash + // force 'gadget', 'flash' X_NET_currentWrapper = X_NET_XHRWrapper || X_TEMP.X_Net_XHR_init(); // OAuth2 diff --git a/0.6.x/js/06_net/01_XNetXHR.js b/0.6.x/js/06_net/01_XNetXHR.js index 490cb6c..843b5dc 100644 --- a/0.6.x/js/06_net/01_XNetXHR.js +++ b/0.6.x/js/06_net/01_XNetXHR.js @@ -33,6 +33,13 @@ Android1.6- の XHR で 401 エラーが返った場合は、iframe に xml を IE9 で 画像バイナリの取得 VBA をかましている http://web.archive.org/web/20130808105151/http://gurimmer.lolipop.jp/daihakken/2012/05/22/javascriptajaxxmlhttprequest%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Fajax%E3%81%AE%E3%82%A8%E3%83%B3%E3%82%B3%E3%83%BC%E3%83%89 http://d.hatena.ne.jp/maachang/20130221/1361427565 + +http://web.archive.org/web/20130531162446/http://gurimmer.lolipop.jp/daihakken/2012/06/25/ajaxjavascript%E3%83%8D%E3%82%A4%E3%83%86%E3%82%A3%E3%83%96xmlhttp%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88%E3%81%A8%E3%81%AF/ + +IE8 以下で xhr の失敗率が高い問題 +http://tkengo-totoro.blogspot.jp/2011/11/iexmlhttprequest.html +TODO クライアント側にもリトライ機構を入れてみる + */ var // Opera7.6+, Safari1.2+, khtml3.?+, Gecko0.9.7+ // ie7&8 ではローカルリソースには ActiveX の XHR を使う @@ -44,7 +51,7 @@ var // Opera7.6+, Safari1.2+, khtml3.?+, Gecko0.9.7+ X_Net_XHR_createXDR = window[ 'XDomainRequest' ] && function(){ return X_Net_XHR_xdr || ( X_Net_XHR_xdr = new XDomainRequest() ); }, X_Net_XHR_xdr = X_Net_XHR_createXDR && X_Net_XHR_createXDR(), - // ie11の互換モード(7,8,5)の msxml はいまいち動かない + // ie11の互換モード(7,8)の msxml はいまいち動かない X_Net_XHR_createMSXML = X_UA[ 'ActiveX' ] && ( X_UA[ 'IE5x' ] || X_UA[ 'IE6' ] || X_URL_IS_LOCAL ) && ( new Function( 'f', [ 'var x=".XMLHTTP",', @@ -74,7 +81,11 @@ if( X_Net_XHR_msXML ){ X_Net_XHR_createMSXML = null; }; -X[ 'Net' ][ 'XHR' ] = { +X[ 'XHR' ] = { + + 'W3C' : X_Net_XHR_createW3C ? 1 : 0, + 'MSXML' : X_Net_XHR_createMSXML ? 2 : 0, + 'XDR' : X_Net_XHR_createXDR ? 4 : 0, /* * http://hakuhin.jp/as/import.html @@ -82,20 +93,24 @@ X[ 'Net' ][ 'XHR' ] = { * http://hakuhin.jp/as/javascript.html * Flash から JavaScript にアクセスする(3+) */ - 'FLASH' : false, + 'FLASH' : X_Pulgin_FLASH_ENABLED && 4 <= X_Pulgin_FLASH_VERSION ? 8 : 0, + + 'GADGET' : 5.5 <= X_UA[ 'IE' ] || !X_UA[ 'IE' ] ? 16 : 0, /** * https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest * Progress Events Chrome7, firefox3.5, ie10, opera12, Safari?, Chrome for Android 0.16 */ - 'PROGRESS' : X_Net_XHR_progress, + 'PROGRESS' : X_Net_XHR_progress, - 'UL_PROGRESS' : X_Net_XHR_upload, + 'UPLOAD_PROGRESS' : X_Net_XHR_upload, - // or gadget proxy - 'CORS' : X_Net_XHR_xdr || ( X_Net_XHR_w3c && X_Net_XHR_w3c.withCredentials !== undefined ) + // or gadget proxy or flash + 'CORS' : X_Net_XHR_xdr || ( X_Net_XHR_w3c && X_Net_XHR_w3c.withCredentials !== undefined ) }; +if( X_Net_XHR_msXMLVer ) X[ 'XHR' ][ 'MSXML_VERSION' ] = X_Net_XHR_msXMLVer; + if( X_Net_XHR_w3c || X_Net_XHR_msXML ){ X_TEMP.X_Net_XHR_init = function(){ @@ -139,7 +154,7 @@ X_TEMP.X_Net_XHR_init = function(){ this._dataType = obj[ 'dataType' ] || X_URL_getEXT( url ); if( !raw || xDomain !== this._isXDR || ( X_Net_XHR_createMSXML && isFile !== this._isMsXML ) ){ - this[ 'unlisten' ]( [ 'load', 'readystatechange', 'progress', 'error', 'timeout' ] ); + raw && this[ 'unlisten' ]( [ 'load', 'readystatechange', 'progress', 'error', 'timeout' ] ); init = true; this[ '_rawObject' ] = raw = xDomain ? X_Net_XHR_createXDR() : @@ -220,6 +235,13 @@ X_TEMP.X_Net_XHR_init = function(){ }; if( !this._isXDR && ( this._isMsXML ? 3 <= X_Net_XHR_msXMLVer : raw.setRequestHeader ) ){ // msxml は setRequestHeader getter がいけない + + /* + if( noCache ){ + headers[ 'Pragma' ] = 'no-cache'; + headers[ 'Cache-Control' ] = 'no-cache'; + headers[ 'If-Modified-Since' ] = 'Thu, 01 Jun 1970 00:00:00 GMT'; + } else */ // http://nakigao.sitemix.jp/blog/?p=2040 // json 取得時に SafariでHTTP/412のエラー。但し相手が audio の場合、この指定があるとロードに失敗する。 iOS8.2, iOS7.1 では遭遇せず if( this._dataType === 'json' ){ @@ -234,12 +256,7 @@ X_TEMP.X_Net_XHR_init = function(){ if( method === 'POST' && !headers[ 'Content-Type' ] ){ headers[ 'Content-Type' ] = 'application/x-www-form-urlencoded'; }; - /* - if( noCache ){ - headers[ 'Pragma' ] = 'no-cache'; - headers[ 'Cache-Control' ] = 'no-cache'; - headers[ 'If-Modified-Since' ] = 'Thu, 01 Jun 1970 00:00:00 GMT'; - }; */ + for( p in headers ){ if( X_EMPTY_OBJECT[ p ] ) continue; @@ -284,7 +301,7 @@ X_TEMP.X_Net_XHR_init = function(){ }, cancel : function(){ - /* X.Net.XHR.CANCELABLE && */ this[ '_rawObject' ].abort && this[ '_rawObject' ].abort(); + /* this[ '_rawObject' ].abort && */ this[ '_rawObject' ].abort(); this._canceled = true; }, @@ -298,7 +315,7 @@ X_TEMP.X_Net_XHR_init = function(){ // XMLHttpRequest の使い方 // http://webos-goodies.jp/archives/50548720.html // XMLHttpRequest オブジェクトを再利用する際も、 abort メソッドを呼び出す必要があるようです。 - this[ '_rawObject' ].abort && this[ '_rawObject' ].abort(); + /* this[ '_rawObject' ].abort && */ this[ '_rawObject' ].abort(); // XMLHttpRequest で順番にリソースを取得する // http://note.chiebukuro.yahoo.co.jp/detail/n16248 @@ -355,7 +372,7 @@ X_TEMP.X_Net_XHR_init = function(){ http://www.semblog.org/msano/archives/000407.html * */ case 'readystatechange' : - //if( !X.Net.XHR.PROGRESS ){ + //if( !X.XHR.PROGRESS ){ switch( raw.readyState ){ case 0 : case 1 : diff --git a/0.6.x/js/06_net/05_XXHRGadget.js b/0.6.x/js/06_net/05_XXHRGadget.js new file mode 100644 index 0000000..61cb641 --- /dev/null +++ b/0.6.x/js/06_net/05_XXHRGadget.js @@ -0,0 +1,176 @@ +/* + * gadgets.io.makeRequest + * + * 1. gadget-iframe を作る。指示を # で渡す。 元文書は frame 内の images の監視を開始する。 + * 1. 通信用 img の src + * + * 2. gadget-iframe が 通信用 img を作る。#ready + * + * 3. 元文書が #ready を受け取ったら、iframe の # を書き換えて指示を送る。指示が長い場合、分割して送る。 + * + * 4. gadget-iframe は 通信用 img の # に結果を書く。コンテンツが長い場合、分割する。 + * + * 5. 元文書は結果を受け取ったことを gadget-iframe の # に書いて伝える。 + * + * + */ + + + +var X_NET_GIMR_canUse = 5.5 <= X_UA[ 'IE' ] || !X_UA[ 'IE' ], + + X_NET_GIMR_iframeName = 'gadgetProxy_' + ( Math.random() * 100000 | 0 ), + + X_NET_GIMR_GADGET_XML_URL = 'http://googledrive.com/host/0B4Y86MXyTfuoVUkwTE54T3V1V1U', + + X_NET_GIMR_GADGET_URL = 'http://www.ig.gmodules.com/gadgets/ifr?url=' + encodeURIComponent( X_NET_GIMR_GADGET_XML_URL ) + '&nocache=1', + + X_NET_GIMR_IMAGE_URL = 'img/opacity0.gif', + + X_NET_GIMR_detection = new Function( 'f,j,i', 'for(j=f.length;j;)try{i=f[--j];return i.location.hash}catch(e){}' ), + + X_NET_GIMR_gadgetIframe, + + X_NET_GIMR_requestOptions, + + X_NET_GIMR_requestOriginal, + + X_NET_GIMR_timerID, + + X_NET_GIMR_phase = 0, + + X_NET_GIMR_lastHashString; + + +function X_NET_GIMR_detectImageOverIframe(){ + var raw = X_NET_GIMR_gadgetIframe[ '_rawObject' ], + iwin, ret; + + if( raw ){ + iwin = raw.contentWindow || ( raw.contentDocument && raw.contentDocument.parentWindow ) || window.frames[ X_NET_GIMR_iframeName ]; + + if( iwin && iwin.frames && iwin.frames.length ){ + ret = X_NET_GIMR_detection( iwin.frames ); + if( ret && ret !== X_NET_GIMR_lastHashString ){ + X_NET_GIMR_lastHashString = ret; + console.log( ret ); + + switch( X_NET_GIMR_phase ){ + case 0 : // init + // TODO 分割 + iwin.location.href = X_NET_GIMR_GADGET_URL + '#' + encodeURIComponent( X_NET_GIMR_toJSONString( X_NET_GIMR_requestOptions ) ); + break; + + case 1 : // after makeRequest > :ok 待ち + iwin.location.href = X_NET_GIMR_GADGET_URL + '#_waiting_'; + break; + + case 2 : // _waiting_ 通信結果待ち + ret = decodeURIComponent( ret.substr( 1 ) ); + ret = window.JSON ? JSON.parse( ret ) : eval( '(' + ret + ')' ); + + X_NET_GIMRWrapper._busy = false; + + X_NET_GIMRWrapper + [ 'asyncDispatch' ]( { + type : ret[ 'errors' ] && ret[ 'errors' ].length ? X_EVENT_ERROR : X_EVENT_SUCCESS, + data : ret + } ); + iwin.location.href = X_NET_GIMR_GADGET_URL + '#_recived_'; + X_NET_GIMR_timerID = X_NET_GIMR_phase = 0; + X_NET_GIMR_lastHashString = ''; + + return X_Callback_UN_LISTEN; + }; + ++X_NET_GIMR_phase; + }; + }; + }; +}; + +// コマンドが長い場合、分割する +function X_NET_GIMR_toJSONString( obj ){ + var json = '', k, v; + for( k in obj ){ + if( json ) json += ','; + v = obj[ k ]; + v = v || v === 0 ? v : null; + json += '"' + k + '":' + ( X_Type_isObject( v ) ? X_NET_GIMR_toJSONString( v ) : X_Type_isString( v ) ? '"' + v + '"' : v ); + }; + console.log( json ); + return '{' + json + '}'; +}; + + +// TODO extend NinjaIframe +X_NET_GIMRWrapper = X_Class_override( + X_EventDispatcher(), + { + + _busy : false, + _canceled : false, + _onloadCount : 0, + + load : function( obj ){ + var k; + //createURL + if( !X_NET_GIMR_gadgetIframe ){ + X_NET_GIMR_gadgetIframe = X_Node_systemNode + .create( 'iframe', { + className : 'hidden-iframe', + name : X_NET_GIMR_iframeName, + id : X_NET_GIMR_iframeName, + src : X_NET_GIMR_GADGET_URL + '#' + encodeURIComponent( + X_NET_GIMR_toJSONString( { 'img' : X_URL_toAbsolutePath( X_NET_GIMR_IMAGE_URL ), 'len' : 1000, 'itvl' : 200 } ) ), + scrolling : 'no', + allowtransparency : 'no', + frameborder : 0, + tabindex : -1 + } ); + }; + + X_NET_GIMR_timerID = X.Timer.add( 100, 0, X_NET_GIMR_detectImageOverIframe ); + + X_NET_GIMR_requestOriginal = X_Object_deepCopy( v ); + + X_NET_GIMR_requestOptions = { + 'CONTENT_TYPE' : 'TEXT', + 'GET_FULL_HEADERS' : true, + 'REFRESH_INTERVAL' : 0 + }; + + for( k in obj ){ + if( v = obj[ k ] ){ + switch( k ){ + case 'postdata' : + X_NET_GIMR_requestOptions[ 'POST_DATA' ] = v; + break; + case 'method' : + X_NET_GIMR_requestOptions[ 'METHOD' ] = v; + break; + case 'dataType' : + X_NET_GIMR_requestOptions[ 'CONTENT_TYPE' ] = v; + break; + case 'headers' : + X_NET_GIMR_requestOptions[ 'HEADERS' ] = X_Object_clone( v ); + break; + case 'cashe' : + X_NET_GIMR_requestOptions[ 'REFRESH_INTERVAL' ] = 3600; + break; + }; + }; + }; + + this._busy = true; + }, + + cancel : function(){ + this._canceled = true; + }, + + reset : function(){ + this._busy = this._canceled = false; + this._onloadCount = 0; + } + } +); diff --git a/0.6.x/js/06_net/10_XOAuth2.js b/0.6.x/js/06_net/10_XOAuth2.js new file mode 100644 index 0000000..f11bb13 --- /dev/null +++ b/0.6.x/js/06_net/10_XOAuth2.js @@ -0,0 +1,312 @@ + +var X_NET_OAUTH2_detection = new Function( 'w', 'try{return w.location.search}catch(e){}' ), + X_NET_OAUTH2_authorizationWindow, + X_NET_OAUTH2_authorizationTimerID; + +/** + * イベント + *
    + *
    X.Event.NEED_AUTH
    window を popup して認可を行う必要あり。ポインターイベント内で oauth2.requestAuth() を呼ぶ。 + *
    X.Event.CANCELED
    認可 window が閉じられた。([x]等でウインドウが閉じられた、oauth2.cancelAuth() が呼ばれた) + *
    X.Event.SUCCESS
    認可 window でユーザーが認可し、続いてコードの認可が済んだ。 + *
    X.Event.ERROR
    コードの認可のエラー、リフレッシュトークンのエラー、ネットワークエラー + *
    X.Event.PROGRESS
    コードを window から受け取った、リフレッシュトークンの開始、コードの認可を header -> params に切替 + *
    + * + * OAuth2 state + *
    + *
    0 :
    disconnected + *
    1 :
    now authentication ... + *
    + :
    authorization_code + *
    2 :
    refresh_token + *
    3 :
    hasAccessToken + *
    + */ +X[ 'OAuth2' ] = X_EventDispatcher[ 'inherits' ]( + 'X.OAuth2', + X_Class.NONE, + + /** @lends OAuth2.prototype */ + { + 'Constructor' : function( obj ){ + + X_Pair_create( this, obj = X_Object_clone( obj ) ); + + if( _getAccessToken( this ) ){ + obj.oauth2State = 3; + } else { + this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH ); + } + + obj.onAuthError = X_NET_OAUTH2_onXHR401Error; + obj.updateRequest = X_NET_OAUTH2_updateRequest; + }, + + 'authState' : function(){ + return X_Pair_get( this ).oauth2State || 0; + }, + + 'requestAuth' : function(){ + // pointer event 内で呼ぶこと + // 二つ以上の popup を作らない + if( X_NET_OAUTH2_authorizationWindow ) return; + + pair = X_Pair_get( this ); + + if( pair.net || pair.oauth2State ) return; + + X_NET_OAUTH2_authorizationWindow = window.open( + pair[ 'authorizeEndpoint' ] + '?' + X_URL_objToParam( + { + response_type : 'code', + client_id : pair[ 'clientID' ], + redirect_uri : tpair[ 'redirectURI' ], + scope : ( pair[ 'scopes' ] || []).join(' ') + } + ), 'oauthauthorize', + 'width=' + pair[ 'authorizeWindowWidth' ] + + ',height=' + pair[ 'authorizeWindowHeight' ] + + ',left=' + (screen.width - pair[ 'authorizeWindowWidth' ] ) / 2 + + ',top=' + (screen.height - pair[ 'authorizeWindowHeight' ] ) / 2 + + ',menubar=no,toolbar=no'); + + X_NET_OAUTH2_authorizationTimerID = X_Timer_add( 333, 0, this, X_Net_OAuth2_detectAuthPopup ); + + pair.oauth2State = 1; + + this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to auth.' } ); + }, + + 'cancelAuth' : function(){ + pair = X_Pair_get( this ); + + if( pair.net ){ + pair.net[ 'kill' ](); + delete pair.net; + }; + + X_NET_OAUTH2_authorizationWindow && X_NET_OAUTH2_authorizationWindow.close(); + X_NET_OAUTH2_authorizationWindow = null; + + X_NET_OAUTH2_authorizationTimerID && X_Timer_remove( X_NET_OAUTH2_authorizationTimerID ); + X_NET_OAUTH2_authorizationTimerID = 0; + + this[ 'asyncDispatch' ]( X_EVENT_CANCELED ); + }, + + 'refreshToken' : function(){ + /* + * var expires_at = this._getAccessTokenExpiry(); + if (expires_at && Date.now() + millis > expires_at) + this._refreshAccessToken({replay: false}); + */ + + pair = X_Pair_get( this ); + + if( pair.net ) return; + + pair.oauth2State = 2; + + pair.net = X.Net( { + 'xhr' : pair[ 'tokenEndpoint' ], + 'postdata' : X_URL_objToParam({ + 'client_id' : pair[ 'clientID' ], + 'client_secret' : pair[ 'clientSecret' ], + 'grant_type' : 'refresh_token', + 'refresh_token' : _getRefreshToken( this ) + }), + 'dataType' : 'json', + 'headers' : { + 'Accept' : 'application/json', + 'Content-Type' : 'application/x-www-form-urlencoded' + } + } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], this, X_Net_OAuth2_responceHandler ); + + this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Start to refresh token.' } ); + } + } + ); + +function X_Net_OAuth2_detectAuthPopup(){ + var closed, search, pair; + + if( window.frames[ 'oauthauthorize' ] !== X_NET_OAUTH2_authorizationWindow || X_NET_OAUTH2_authorizationWindow.closed ){ + pair.oauth2State = 0; + closed = true; + this[ 'asyncDispatch' ]( X_EVENT_CANCELED ); + } else + if( search = X_NET_OAUTH2_detection( X_NET_OAUTH2_authorizationWindow ) ){ + pair = X_Pair_get( this ); + pair.code = X_URL_ParamToObj( search.slice( 1 ) )[ 'code' ]; + + X_NET_OAUTH2_authorizationWindow.close(); + closed = true; + + X_Net_OAuth2_authorizationCode( this, pair ); + + this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Get code success, then authorization code.' } ); + }; + + if( closed ){ + X_NET_OAUTH2_authorizationWindow = null; + X_NET_OAUTH2_authorizationTimerID = 0; + + return X_Callback_UN_LISTEN; + }; +}; + +function X_Net_OAuth2_authorizationCode( oauth2, pair ){ + pair.net = X.Net( { + 'xhr' : pair[ 'tokenEndpoint' ], + 'postdata' : X_URL_objToParam({ + 'client_id' : pair[ 'clientID' ], + 'client_secret' : pair[ 'clientSecret' ], + 'grant_type' : 'authorization_code', + 'code' : pair.code, + 'redirect_uri' : pair[ 'redirectURI' ] + }), + 'dataType' : 'json', + 'headers' : { + 'Accept' : 'application/json', + 'Content-Type' : 'application/x-www-form-urlencoded' + } + } ).listenOnce( [ X_EVENT_SUCCESS, X_EVENT_ERROR ], oauth2, X_Net_OAuth2_responceHandler ); +}; + +function X_Net_OAuth2_responceHandler( e ){ + var data = e.data, + pair = X_Pair_get( this ), + isRefresh = pair.oauth2State === 2; + + delete pair.net; + + switch( e.type ){ + case X_EVENT_SUCCESS : + if( isRefresh && data.error ){ + _removeRefreshToken( this ); + pair.oauth2State = 0; + this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' + data.error, data : data } ); + this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH ); + return; + } else + if( data.error ){ + pair.oauth2State = 0; + this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Get new access token error.' + data.error, data : data } ); + this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH ); + return; + }; + + _setAccessToken( this, data[ 'access_token' ] || '' ); + _setRefreshToken( this, data[ 'refresh_token' ] || '' ); + + if( data[ 'expires_in' ] ){ + _setAccessTokenExpiry( this, X_Timer_now() + data[ 'expires_in' ] * 1000 ); + } else + if( _getAccessTokenExpiry( this ) ){ + _removeAccessTokenExpiry( this ); + }; + + pair.oauth2State = 3; + this[ 'asyncDispatch' ]( { type : X_EVENT_SUCCESS, message : isRefresh ? 'Refresh access token success.' : 'Get new access token success.' } ); + break; + + case X_EVENT_ERROR : + if( isRefresh ){ + // other error, not auth + pair.oauth2State = 0; + this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'Refresh access token error.' } ); + _removeRefreshToken( this ); + this[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH ); + } else + if( _getAuthMechanism( this ) === 'param' ){ + pair.oauth2State = 0; + this[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'network-error' } ); + } else { + pair.oauth2State = 0; + _setAuthMechanism( 'param' ); + this[ 'asyncDispatch' ]( { type : X_EVENT_PROGRESS, message : 'Refresh access token failed. retry header -> param. ' } ); + // retry + X_Net_OAuth2_authorizationCode( this, pair ); + }; + break; + }; +}; + +function X_NET_OAUTH2_onXHR401Error( oauth2 ){ + var pair = this, + xhr, bearerParams, headersExposed = false; + + if( _getAuthMechanism( oauth2 ) !== 'param' ){ + xhr = X_NET_currentWrapper[ '_rawObject' ]; + bearerParams = xhr.getResponseHeader( 'WWW-Authenticate' ); + headersExposed = !X_Net_XHR_X_DOMAIN || !!xhr.getAllResponseHeaders(); // this is a hack for Firefox and IE + }; + + // http://d.hatena.ne.jp/ritou/20110402/1301679908 + if ( bearerParams && bearerParams.indexOf( ' error="' ) === -1 ) { + pair.oauth2State = 0; + oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH ); + } else + if ((( bearerParams && bearerParams.indexOf( ' error="invalid_token"' ) !== -1 ) || !headersExposed) && _getRefreshToken( oauth2 ) ) { + _removeAccessToken( oauth2 ); // It doesn't work any more. + pair.oauth2State = 2; + oauth2.refreshToken(); + } else + if (!headersExposed && !_getRefreshToken( oauth2 )) { + pair.oauth2State = 0; + oauth2[ 'asyncDispatch' ]( X_EVENT_NEED_AUTH ); + }; +}; + +function X_NET_OAUTH2_updateRequest( oauth2, request ){ + var token = _getAccessToken( oauth2 ), + mechanism = _getAuthMechanism( oauth2 ), + url = request[ 'url' ], + headers; + + if( token && mechanism === 'param'){ + request[ 'url' ] = url + ((url.indexOf('?') !== -1) ? '&' : '?') + 'bearer_token=' + encodeURIComponent( token ); + }; + + if( token && ( !mechanism || mechanism === 'header' ) ){ + request[ 'headers' ] || ( headers = request[ 'headers' ] = {} ); + headers[ 'Authorization' ] = 'Bearer ' + token; + }; +}; + +function _getAccessToken( that ){ return updateLocalStorage( '', that, 'accessToken' ); } +function _getRefreshToken( that){ return updateLocalStorage( '', that, 'refreshToken' ); } +function _getAccessTokenExpiry( that ){ return updateLocalStorage( '', that, 'tokenExpiry' ); } +function _getAuthMechanism( that ){ + // IE's XDomainRequest doesn't support sending headers, so don't try. + return X_Net_XHR_X_DOMAIN ? 'param' : updateLocalStorage( '', that, 'AuthMechanism' ); + } +function _setAccessToken( that, value ){ updateLocalStorage( '+', that, 'accessToken' , value); } +function _setRefreshToken( that, value ){ updateLocalStorage( '+', that, 'refreshToken', value); } +function _setAccessTokenExpiry( that, value ){ updateLocalStorage( '+', that, 'tokenExpiry', value); } +function _setAuthMechanism( that, value ){ updateLocalStorage( '+', that, 'AuthMechanism', value); } + +function _removeAccessToken( that ){ updateLocalStorage( '-', that, 'accessToken' ); } +function _removeRefreshToken( that ){ updateLocalStorage( '-', that, 'refreshToken' ); } +function _removeAccessTokenExpiry( that ){ updateLocalStorage( '-', that, 'tokenExpiry' ); } +function _removeAuthMechanism( that ){ updateLocalStorage( '-', that, 'AuthMechanism' ); } + +function updateLocalStorage( cmd, that, name, value ){ + var action = cmd === '+' ? 'setItem' : '-' ? 'removeItem' : 'getItem', + pair; + + if( window.localStorage ){ + return window.localStorage[ action ]( X_Pair_get( that )[ 'clientID' ] + name, value ); + }; + + pair = X_Pair_get( that ); + switch( cmd ){ + case '+' : + pair[ name ] = value; + break; + case '-' : + if( pair[ name ] !== undefined ) delete pair[ name ]; + }; + return pair[ name ]; +}; + diff --git a/0.6.x/js/07_audio/00_XAudio.js b/0.6.x/js/07_audio/00_XAudio.js index acae436..e923109 100644 --- a/0.6.x/js/07_audio/00_XAudio.js +++ b/0.6.x/js/07_audio/00_XAudio.js @@ -10,15 +10,7 @@ QuickTime : 8, */ -var X_Audio_BACKENDS = [], // Array. - X_Audio_WRAPPER_LIST = []; // Array. - -function X_Audio_getAudioWrapper( proxy ){ - var i = X_Audio_WRAPPER_LIST.length; - for( ; i; ){ - if( X_Audio_WRAPPER_LIST[ --i ].proxy === proxy ) return X_Audio_WRAPPER_LIST[ i ]; - }; -}; +var X_Audio_BACKENDS = []; // Array. /* * X_EVENT_BACKEND_READY @@ -40,13 +32,13 @@ function X_Audio_getAudioWrapper( proxy ){ * X_EVENT_MEDIA_SEEKING シーク中に音声が待機状態に。間もなく X_EVENT_MEDIA_PLAYING に移行。 */ +// TODO この内容は、AudioBackend の Abstract クラスにする。AudioSprite は Audio ではなく AudioBackend をマネージする X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( 'X.Audio', X_Class.POOL_OBJECT, { 'source' : '', 'backendName' : '', - _backend : -1, 'Constructor' : function( sourceList, opt_option ){ X_Audio_startDetectionBackend( @@ -57,41 +49,27 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( }, 'play' : function( startTime, endTime, loop, loopStartTime, loopEndTime ){ - var state, duration; - if( 0 <= startTime ){ - this[ 'state' ]( { - currentTime : startTime, - startTime : startTime, - endTime : endTime, - loop : loop, - loopStartTime : loopStartTime, - loopEndTime : loopEndTime - } ); - }; - this._backend !== -1 && X_Audio_getAudioWrapper( this ).play(); + var pair = X_Pair_get( this ); + pair && pair.play( startTime, endTime, loop, loopStartTime, loopEndTime ); return this; }, 'seek' : function( seekTime ){ - var state = this[ 'state' ](), - end = X_AudioWrapper_getEndTime( X_Audio_getAudioWrapper( this ) ); - if( seekTime < end ){ - this[ 'state' ]( { currentTime : seekTime } ); - }; + var pair = X_Pair_get( this ); + pair && pair.seek( seekTime ); return this; }, 'pause' : function(){ - this[ 'state' ]().playing && X_Audio_getAudioWrapper( this ).pause(); + var pair = X_Pair_get( this ); + pair && pair.pause(); return this; }, 'state' : function( obj ){ - var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this ); - + var pair = X_Pair_get( this ); if( obj === undefined ){ - return backend ? - backend.state() : + return pair ? pair.getState() : { 'startTime' : -1, 'endTime' : -1, @@ -102,58 +80,51 @@ X[ 'Audio' ] = X_EventDispatcher[ 'inherits' ]( 'looded' : false, 'error' : false, 'playing' : false, - 'source' : this[ 'source' ] || '', 'duration' : 0 }; }; - backend && backend.state( obj ); + pair && pair.setState( obj ); return this; }, 'loop' : function( v ){ - var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this ); - if( v === undefined ){ - return backend && backend.state().loop; - }; - backend && backend.state( { loop : v } ); + var pair = X_Pair_get( this ); + pair && pair.loop( v ); return this; }, 'volume' : function( v ){ - var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this ); - if( v === undefined ){ - return backend && backend.state().volume; - }; - backend && backend.state( { volume : v } ); + var pair = X_Pair_get( this ); + pair && pair.volume( v ); return this; }, 'currentTime' : function( v ){ - var backend = this._backend !== -1 && X_Audio_getAudioWrapper( this ); - if( v === undefined ){ - return backend && backend.state().currentTime; - }; - backend && backend.state( { currentTime : v } ); + var pair = X_Pair_get( this ); + pair && pair.currentTime( v ); return this; }, 'isPlaying' : function(){ - return this._backend !== -1 && X_Audio_getAudioWrapper( this ).state().playing; + var pair = X_Pair_get( this ); + return pair && pair.playing; } } ); function X_Audio_handleEvent( e ){ + var backend; + switch( e.type ){ case X_EVENT_BACKEND_READY : + backend = X_Audio_BACKENDS[ e[ 'backendID' ] ]; + this[ 'unlisten' ]( X_EVENT_BACKEND_NONE, X_Audio_handleEvent ); - this[ 'source' ] = e.source; - this[ 'backendName' ] = X_Audio_BACKENDS[ this._backend ].backendName; - X_Audio_WRAPPER_LIST.push( - new X_Audio_BACKENDS[ this._backend ] - .klass( this, e.source, e.option ) ); + this[ 'source' ] = e[ 'source' ]; + this[ 'backendName' ] = backend.backendName; + X_Pair_create( this, backend.klass( this, e[ 'source' ], e[ 'option' ] ) ); break; case X_EVENT_BACKEND_NONE : @@ -161,7 +132,9 @@ function X_Audio_handleEvent( e ){ break; case X_EVENT_KILL_INSTANCE : - this._backend !== -1 && X_Audio_getAudioWrapper( this ).close(); + backend = X_Pair_get( this ); + backend && backend[ 'kill' ](); + X_Pair_release( this, backend ); break; }; }; @@ -171,126 +144,256 @@ function X_Audio_handleEvent( e ){ * TODO preplayerror play してみたら error が出た、backend の変更。 */ -function X_Audio_startDetectionBackend( backend, proxy, sourceList, option ){ +function X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ){ var source = sourceList[ 0 ] || '', ext = X_URL_getEXT( source ), sup; if( source && backend ){ - sup = [ proxy, sourceList, option, source, ext ]; + sup = [ xaudio, sourceList, option, source, ext ]; sup[ 5 ] = sup; - proxy[ 'listenOnce' ]( X_EVENT_COMPLETE, backend, X_Audio_onEndedDetection, sup ); - backend.detect( proxy, source, ext ); + xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, backend, X_Audio_onEndedDetection, sup ); + backend.detect( xaudio, source, ext ); } else { - proxy[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); + xaudio[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); }; }; -function X_Audio_onEndedDetection( e, proxy, sourceList, option, source, ext, sup ){ +function X_Audio_onEndedDetection( e, xaudio, sourceList, option, source, ext, sup ){ var i = X_Audio_BACKENDS.indexOf( this ), backend; if( e.canPlay ){ - proxy._backend = i; - proxy[ 'asyncDispatch' ]( { + xaudio._backend = i; + xaudio[ 'asyncDispatch' ]( { type : X_EVENT_BACKEND_READY, 'option' : option, 'source' : source, - 'backendName' : this[ 'backendName' ] + 'backendName' : this[ 'backendName' ], + 'backendID' : i } ); } else { console.log( 'No ' + source + ' ' + this[ 'backendName' ] ); if( sup[ 3 ] = source = sourceList[ sourceList.indexOf( source ) + 1 ] ){ sup[ 4 ] = ext = X_URL_getEXT( source ); - proxy[ 'listenOnce' ]( X_EVENT_COMPLETE, this, X_Audio_onEndedDetection, sup ); - this.detect( proxy, source, ext ); + xaudio[ 'listenOnce' ]( X_EVENT_COMPLETE, this, X_Audio_onEndedDetection, sup ); + this.detect( xaudio, source, ext ); } else if( backend = X_Audio_BACKENDS[ i + 1 ] ){ - X_Audio_startDetectionBackend( backend, proxy, sourceList, option ); + X_Audio_startDetectionBackend( backend, xaudio, sourceList, option ); } else { - proxy[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); + xaudio[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); }; }; }; -function X_AudioWrapper_updateStates( audioWrapper, obj ){ - var playing = audioWrapper.playing, - k, v, - end = 0, seek = 0, volume = 0; - - for( k in obj ){ - v = obj[ k ]; - switch( k ){ - case 'currentTime' : - v = X_AudioWrapper_timeStringToNumber( v ); - if( X_Type_isNumber( v ) ){ - if( playing ){ - if( audioWrapper.state().currentTime !== v ){ - audioWrapper.seekTime = v; - seek = 2; +var X_Audio_AbstractAudioBackend = X_EventDispatcher[ 'inherits' ]( + 'X.AbstractAudioBackend', + X_Class.ABSTRACT, + { + + url : '', + target : null, + + startTime : 0, + endTime : -1, + loopStartTime : -1, + loopEndTime : -1, + seekTime : -1, + duration : 0, + + playing : false, + error : 0, + autoLoop : false, + looped : false, + autoplay : false, + gain : 0.5, + + play : function( startTime, endTime, loop, loopStartTime, loopEndTime ){ + if( 0 <= startTime ){ + this.setState( { + currentTime : startTime, + startTime : startTime, + endTime : endTime, + loop : loop, + loopStartTime : loopStartTime, + loopEndTime : loopEndTime + } ); + }; + this.actualPlay(); + }, + + seek : function( seekTime ){ + if( seekTime < X_AudioWrapper_getEndTime( this ) ){ + this.setState( { currentTime : seekTime } ); + }; + }, + + pause : function(){ + this.playing && this.actualPause(); + }, + + loop : function( v ){ + if( v === undefined ){ + return this.autoLoop; + }; + this.setState( { loop : v } ); + }, + + volume : function( v ){ + if( v === undefined ){ + return this.gain; + }; + this.setState( { volume : v } ); + }, + + currentTime : function( v ){ + if( v === undefined ){ + return this.playing ? this.getActualCurrentTime() : this.seekTime; + }; + this.setState( { currentTime : v } ); + }, + + getState : function(){ + + return { + 'startTime' : this.startTime, + 'endTime' : this.endTime < 0 ? this.duration : this.endTime, + 'loopStartTime' : this.loopStartTime < 0 ? this.startTime : this.loopStartTime, + 'loopEndTime' : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime, + 'loop' : this.autoLoop, + 'looped' : this.looped, + 'volume' : this.gain, + 'playing' : this.playing, + 'duration' : this.duration, + + 'currentTime' : this.playing ? this.getActualCurrentTime() : this.seekTime, + 'error' : this.getActualError ? this.getActualError() : this.error + }; + }, + + setState : function( obj ){ + var playing = this.playing, + k, v, + end = 0, seek = 0, volume = 0; + + for( k in obj ){ + v = obj[ k ]; + switch( k ){ + case 'currentTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( X_Type_isNumber( v ) ){ + if( playing ){ + if( this.getActualCurrentTime() !== v ){ + seek = 2; + this.seekTime = v; + }; + } else { + this.seekTime = v; + }; + } else { + continue; }; - } else { - audioWrapper.seekTime = v; - }; - } else { - continue; - }; - break; + break; + + case 'startTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.startTime !== v ){ + this.startTime = v; + }; + } else { + delete this.startTime; + }; + break; - case 'startTime' : - case 'endTime' : - case 'loopStartTime' : - case 'loopEndTime' : - v = X_AudioWrapper_timeStringToNumber( v ); - console.log( k + ' ' + v ); - if( v || v === 0 ){ - if( audioWrapper[ k ] !== v ){ - audioWrapper[ k ] = v; + case 'endTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.endTime !== v ){ + this.endTime = v; + if( playing ) end = 1; + }; + } else { + delete this.endTime; + if( playing ) end = 1; + }; + break; - // 再生中の endTime の変更 - if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1; - }; - } else { - delete audioWrapper[ k ]; - if( playing && ( k === 'endTime' || k === 'loopEndTime' ) ) end = 1; - }; - break; - - case 'looped' : - if( playing ) seek = 2; - case 'loop' : - case 'autoplay' : - if( X_Type_isBoolean( v ) && audioWrapper[ k ] !== v ){ - audioWrapper[ k ] = v; + case 'loopStartTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.loopStartTime !== v ){ + this.loopStartTime = v; + }; + } else { + delete this.loopStartTime; + }; + break; + + case 'loopEndTime' : + v = X_AudioWrapper_timeStringToNumber( v ); + if( v || v === 0 ){ + if( this.loopEndTime !== v ){ + this.loopEndTime = v; + if( playing ) end = 1; + }; + } else { + delete this.loopEndTime; + if( playing ) end = 1; + }; + break; + + case 'looped' : + if( X_Type_isBoolean( v ) && this.looped !== v ){ + this.looped = v; + if( playing ) seek = 2; + }; + break; + + case 'loop' : + if( X_Type_isBoolean( v ) && this.autoLoop !== v ){ + this.autoLoop = v; + }; + break; + + case 'autoplay' : + if( X_Type_isBoolean( v ) && this.autoplay !== v ){ + this.autoplay = v; + }; + break; + + case 'volume' : + if( X_Type_isNumber( v ) ){ + v = v < 0 ? 0 : 1 < v ? 1 : v; + if( this.gain !== v ){ + this.gain = v; + // if playing -> update + if( playing ) volume = 4; + }; + }; + break; }; - break; + }; + + if( this.endTime < this.startTime || + ( this.loopEndTime < 0 ? this.endTime : this.loopEndTime ) < ( this.loopStartTime < 0 ? this.startTime : this.loopStartTime ) || + X_AudioWrapper_getEndTime( this ) < this.seekTime// || + //this.duration < this.endTime + ){ + console.log( 'setState 0:' + this.startTime + ' -> ' + this.endTime + ' d:' + this.duration + ' 1:' + this.loopStartTime + ' -> ' + this.loopEndTime ); + return; + }; + + v = end + seek + volume; + return v && this.afterUpdateState( v ); + } + + } +); - case 'volume' : - if( X_Type_isNumber( v ) ){ - v = v < 0 ? 0 : 1 < v ? 1 : v; - if( audioWrapper[ k ] !== v ){ - audioWrapper[ k ] = v; - // if playing -> update - if( playing ) volume = 4; - }; - }; - break; - }; - }; - - if( audioWrapper.endTime < audioWrapper.startTime || - ( audioWrapper.loopEndTime < 0 ? audioWrapper.endTime : audioWrapper.loopEndTime ) < ( audioWrapper.loopStartTime < 0 ? audioWrapper.startTime : audioWrapper.loopStartTime ) || - X_AudioWrapper_getEndTime( audioWrapper ) < audioWrapper.seekTime// || - //audioWrapper.duration < audioWrapper.endTime - ){ - console.log( 'error @updateStateObject() begin:' + audioWrapper.startTime + ' end:' + audioWrapper.endTime + ' d:' + audioWrapper.duration + ' ls:' + audioWrapper.loopStartTime ); - return 0; - }; - - return end + seek + volume; -}; function X_AudioWrapper_timeStringToNumber( time ){ var ary, ms, s = 0, m = 0, h = 0; diff --git a/0.6.x/js/07_audio/01_XWebAudio.js b/0.6.x/js/07_audio/01_XWebAudio.js index 958b441..00cf9c3 100644 --- a/0.6.x/js/07_audio/01_XWebAudio.js +++ b/0.6.x/js/07_audio/01_XWebAudio.js @@ -2,6 +2,7 @@ var X_Audio_WebAudio_context = !X_UA[ 'iPhone_4s' ] && !X_UA[ 'iPad_2Mini1' ] && !X_UA[ 'iPod_4' ] && !( X_UA[ 'Gecko' ] && X_UA[ 'Android' ] ) && ( window.AudioContext || window.webkitAudioContext ), + X_Audio_BUFFER_LIST = [], X_Audio_WebAudioWrapper; /* @@ -11,80 +12,34 @@ if( X_Audio_WebAudio_context ){ X_Audio_WebAudio_context = new X_Audio_WebAudio_context; - function X_Audio_WebAudio_getBuffer( url ){ - var i = 0, l = X_Audio_WRAPPER_LIST.length; - for( i = 0; i < l; ++i ){ - if( X_Audio_WRAPPER_LIST[ i ].url === url ) return X_Audio_WRAPPER_LIST[ i ]; - }; - }; - - X_Audio_WebAudioWrapper = X_EventDispatcher[ 'inherits' ]( - 'X.AV.WebAudioWrapper', + X_Audio_BufferLoader = X_EventDispatcher[ 'inherits' ]( + 'X.AV.WebAudioBufferLoader', X_Class.POOL_OBJECT, { - url : '', - proxy : null, - - startTime : 0, - endTime : -1, - loopStartTime : -1, - loopEndTime : -1, - seekTime : -1, - duration : 0, - - playing : false, - error : 0, - loop : false, - looped : false, - autoplay : false, - volume : 0.5, - - _startPos : 0, - _endPosition : 0, - _startTime : 0, - _timerID : 0, - _interval : 0, - buffer : null, - source : null, - gainNode : null, - _onended : null, - xhr : null, onDecodeSuccess : null, onDecodeError : null, - Constructor : function( proxy, url, option ){ - var audio = X_Audio_WebAudio_getBuffer( url ); - - this.proxy = proxy; - this.url = url; - - X_AudioWrapper_updateStates( this, option ); - - if( audio && audio.buffer ){ - this._onDecodeSuccess( audio.buffer ); - } else - if( audio ){ - // TODO 当てにしていたaudioがclose 等した場合 - audio.proxy[ 'listenOnce' ]( 'canplaythrough', this, this._onBufferReady ); - } else { - this.xhr = X.Net( { 'xhr' : url, 'type' : 'arraybuffer' } ) + buffer : null, + error : 0, + webAudioList : null, + + Constructor : function( webAudio, url ){ + this.webAudioList = [ webAudio ]; + this.url = url; + this.xhr = X.Net( { 'xhr' : url, 'dataType' : 'arraybuffer' } ) [ 'listen' ]( X_EVENT_PROGRESS, this ) - [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE, X_EVENT_CANCELED ], this ); - }; + [ 'listenOnce' ]( [ X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this ); }, handleEvent : function( e ){ switch( e.type ){ case X_EVENT_PROGRESS : - e[ 'percent' ] ? - this.proxy[ 'dispatch' ]( { type : 'progress', 'percent' : e[ 'percent' ] } ) : - this.proxy[ 'dispatch' ]( 'loadstart' ); + this[ 'dispatch' ]( { type : 'progress', 'percent' : e[ 'percent' ] } ); return; case X_EVENT_SUCCESS : - console.log( 'WebAudio xhr success! ' + !!X_Audio_WebAudio_context.decodeAudioData + ' t:' + typeof e.data ); // TODO 旧api // https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext @@ -104,17 +59,12 @@ if( X_Audio_WebAudio_context ){ }; break; - case X_EVENT_CANCELED : - this.error = 1; - this.proxy[ 'dispatch' ]( 'aborted' ); - break; - case X_EVENT_COMPLETE : - this.error = 2; - this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'xhr error' } ); + this.error = 1; + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); break; }; - this.xhr[ 'unlisten' ]( [ X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_COMPLETE, X_EVENT_CANCELED ], this ); + this.xhr[ 'unlisten' ]( [ X_EVENT_PROGRESS, X_EVENT_SUCCESS, X_EVENT_COMPLETE ], this ); delete this.xhr; }, @@ -124,30 +74,23 @@ if( X_Audio_WebAudio_context ){ this.onDecodeSuccess && this._onDecodeComplete(); if ( !buffer ) { - this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'buffer is ' + buffer } ); + this.error = 2; + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); return; }; this.buffer = buffer; - this.duration = buffer.duration * 1000; - /* - this.proxy[ 'asyncDispatch' ]( 'loadedmetadata' ); - this.proxy[ 'asyncDispatch' ]( 'loadeddata' ); - this.proxy[ 'asyncDispatch' ]( 'canplay' ); - this.proxy[ 'asyncDispatch' ]( 'canplaythrough' ); - */ - this.proxy[ 'asyncDispatch' ]( X_EVENT_READY ); - - this.autoplay && X_Timer_once( 16, this, this.play ); - + + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); + console.log( 'WebAudio decoded!' ); }, _onDecodeError : function(){ console.log( 'WebAudio decode error!' ); this._onDecodeComplete(); - this.error = 3; - this.proxy[ 'asyncDispatch' ]( { type : X_EVENT_ERROR, message : 'decode error' } ); + this.error = 2; + this[ 'asyncDispatch' ]( X_EVENT_COMPLETE ); }, _onDecodeComplete : function(){ @@ -156,36 +99,100 @@ if( X_Audio_WebAudio_context ){ X_Callback_correct( this.onDecodeError ); delete this.onDecodeError; }, - - _onBufferReady : function( e ){ - var audio = X_Audio_WebAudio_getBuffer( this.url ); - this._onDecodeSuccess( audio.buffer ); - }, - close : function(){ - delete this.buffer; - - if( this.xhr ) this.xhr.close(); - - if( this.onDecodeSuccess ){ - // 回収はあきらめる、、、 + unregister : function( webAudio ){ + var list = this.webAudioList, + i = list.indexOf( webAudio ); + if( 0 < i ){ + list.splice( i, 1 ); + if( list.length ){ + this.xhr && this.xhr[ 'kill' ](); + this[ 'kill' ](); + }; }; + } + + } + ); - this.playing && this.pause(); - this.source && this._sourceDispose(); - this._onended && X_Callback_correct( this._onended ); - - this.gainNode && this.gainNode.disconnect(); - }, + X_Audio_WebAudioWrapper = X_Audio_AbstractAudioBackend[ 'inherits' ]( + 'X.AV.WebAudioWrapper', + X_Class.POOL_OBJECT, + { - _sourceDispose : function(){ - this.source.disconnect(); - delete this.source.onended; - delete this.source; - }, + loader : null, + + _startPos : 0, + _endPosition : 0, + _startTime : 0, + _timerID : 0, + _interval : 0, + buffer : null, + source : null, + gainNode : null, + _onended : null, + + Constructor : function( target, url, option ){ + var i = 0, + l = X_Audio_BUFFER_LIST.length, + loader; + + for( ; i < l; ++i ){ + loader = X_Audio_BUFFER_LIST[ i ]; + if( loader.url === url ){ + this.loader = loader; + loader.webAudioList.push( this ); + break; + }; + }; + + if( !this.loader ){ + this.loader = loader = new X_Audio_BufferLoader( this, url ); + }; + + this.target = target || this; + + this.setState( option ); + + this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, X_WebAudio_handleEvent ); + + if( loader.buffer || loader.error ){ + this._onLoadBufferComplete(); + } else { + loader[ 'listenOnce' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ); + }; + }, + _onLoadBufferComplete : function( e ){ + var loader = this.loader, + buffer = loader.buffer; + + e && loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ); + + if ( !buffer ) { + this.error = loader.error; + + this.target[ 'dispatch' ]({ + type : X_EVENT_ERROR, + error : loader.error, + message : loader.error === 1 ? + 'load buffer network error' : + 'buffer decode error' + }); + this[ 'kill' ](); + return; + }; + + this.buffer = buffer; + this.duration = buffer.duration * 1000; + + this.target[ 'asyncDispatch' ]( X_EVENT_READY ); + + this.autoplay && X_Timer_once( 16, this, this.play ); + + }, - play : function(){ + actualPlay : function(){ var begin, end; if( !this.buffer ){ @@ -207,7 +214,7 @@ if( X_Audio_WebAudio_context ){ this.source.buffer = this.buffer; this.source.connect( this.gainNode ); - this.gainNode.gain.value = this.volume; + this.gainNode.gain.value = this.gain; // おかしい、stop 前に外していても呼ばれる、、、@Firefox33.1 // 破棄された X.Callback が呼ばれて、obj.proxy() でエラーになる。Firefox では、onended は使わない @@ -231,13 +238,19 @@ if( X_Audio_WebAudio_context ){ this._startTime = X_Audio_WebAudio_context.currentTime * 1000; this._interval = this._interval || X_Timer_add( 1000, 0, this, this._onInterval ); }, + + _sourceDispose : function(){ + this.source.disconnect(); + delete this.source.onended; + delete this.source; + }, _onInterval : function(){ if( !this.playing ){ delete this._interval; return X_Callback_UN_LISTEN; }; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); }, _onEnded : function(){ @@ -259,25 +272,25 @@ if( X_Audio_WebAudio_context ){ }; }; - if( this.loop ){ - if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ + if( this.autoLoop ){ + if( !( this.target[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ this.looped = true; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); - this.play(); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.actualPlay(); }; } else { - this.pause(); - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); + this.actualPause(); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); }; }; }, - pause : function(){ + actualPause : function(){ if( !this.playing ) return this; console.log( '[WebAudio] pause' ); - this.seekTime = this.state().currentTime; + this.seekTime = this.getActualCurrentTime(); this._timerID && X_Timer_remove( this._timerID ); delete this._timerID; @@ -290,40 +303,42 @@ if( X_Audio_WebAudio_context ){ this.source.stop( 0 ) : this.source.noteOff( 0 ); }; }, - - state : function( obj ){ - var result; - - if( obj === undefined ){ - return { - startTime : this.startTime, - endTime : this.endTime < 0 ? this.duration : this.endTime, - loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime, - loopEndTime : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime, - loop : this.loop, - looped : this.looped, - volume : this.volume, - playing : this.playing, - duration : this.duration, - - currentTime : this.playing ? ( X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0 ) : this.seekTime, - error : this.error - }; - }; - result = X_AudioWrapper_updateStates( this, obj ); - + getActualCurrentTime : function(){ + return X_Audio_WebAudio_context.currentTime * 1000 - this._startTime + this._startPos | 0; + }, + + afterUpdateState : function( result ){ if( result & 2 || result & 1 ){ // seek - this.play(); + this.actualPlay(); } else if( result & 4 ){ - this.gainNode.gain.value = this.volume; + this.gainNode.gain.value = this.gain; }; } } ); + function X_WebAudio_handleEvent( e ){ + switch( e.type ){ + + case X_EVENT_KILL_INSTANCE : + this.loader[ 'unlisten' ]( X_EVENT_COMPLETE, this, this._onLoadBufferComplete ) + .unregister( this ); + + delete this.buffer; + + this.playing && this.actualPause(); + this.source && this._sourceDispose(); + + this._onended && X_Callback_correct( this._onended ); + + this.gainNode && this.gainNode.disconnect(); + break; + }; + }; + /* * http://qiita.com/sou/items/5688d4e7d3a37b4e2ff1 * L-01F 等の一部端末で Web Audio API の再生結果に特定条件下でノイズが混ざることがある。 diff --git a/0.6.x/js/07_audio/02_XHTMLAudio.js b/0.6.x/js/07_audio/02_XHTMLAudio.js index 6da87e2..fcf91f4 100644 --- a/0.6.x/js/07_audio/02_XHTMLAudio.js +++ b/0.6.x/js/07_audio/02_XHTMLAudio.js @@ -10,7 +10,7 @@ var X_Audio_HTMLAudio_playTrigger = X_UA[ 'iOS' ] ? 'suspend' : X_UA[ 'AndroidBrowser2' ] ? 'stalled' : // Android 2.3.5(SBM101SH) では stalled は発生しない,,, X_UA[ 'AndroidBrowser4' ] ? 'loadeddata' : - X_UA[ 'OperaMobile' ] || X_UA[ 'OperaTablet' ] ? 'loadeddata' : 'canplay', + X_UA[ 'OperaMobile' ] || X_UA[ 'OperaTablet' ] ? 'loadeddata' : 'loadeddata', //'canplay', X_Audio_HTMLAudioWrapper, X_Audio_constructor = window[ 'Audio' ] || window.HTMLAudioElement, X_Audio_rawAudio, @@ -30,7 +30,8 @@ var X_Audio_HTMLAudio_playTrigger = X_Audio_HTMLAudioWrapper_ieMobile9Fix = ( X_UA[ 'WinPhone' ] && X_UA[ 'IE9' ] ), X_Audio_HTMLAudioWrapper_durationFix = ( !X_Audio_HTMLAudioWrapper_currentTimeFix && 12 <= X_UA[ 'Opera' ] ), - X_Audio_HTMLAudioWrapper_shortPlayFix = X_UA[ 'AndroidBrowser2' ], + X_Audio_HTMLAudioWrapper_shortPlayFix = //X_UA[ 'AndroidBrowser2' ], + X_UA[ 'AndroidBrowser' ] && X_UA[ 'AndroidBrowserWebkit' ] < 534.3, // Android 4.1.1 でも遭遇 X_Audio_codecs; @@ -71,27 +72,10 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ }; }; - X_Audio_HTMLAudioWrapper = X_EventDispatcher[ 'inherits' ]( + X_Audio_HTMLAudioWrapper = X_Audio_AbstractAudioBackend[ 'inherits' ]( 'X.AV.HTML5AudioWrapper', X_Class.POOL_OBJECT, { - - proxy : null, - - startTime : 0, - endTime : -1, - loopStartTime : -1, - loopEndTime : -1, - seekTime : -1, - duration : 0, - - playing : false, - error : 0, - loop : false, - looped : false, - autoplay : false, - volume : 0.5, - _playTime : 0, _closed : true, _loaded : false, @@ -100,26 +84,26 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ _lastCurrentTime : 0, _src : '', - Constructor : function( proxy, source, option ){ + Constructor : function( target, source, option ){ var raw; - this.proxy = proxy; + this.target = target || this; this._closed = false; - X_AudioWrapper_updateStates( this, option ); + this.setState( option ); if( option[ 'useVideo' ] ){ - this[ '_rawObject' ] = raw = document.createElement( 'video' ); + this[ '_rawObject' ] = raw = document.createElement( 'video' ); raw.preload = 'none'; // auto, metadata, none //raw.autoplay = false, // no-auto - raw.loop = false; - raw.muted = false; - //raw.crossorigin = option[ 'crossorigin' ] || ''; //crossorigin: "anonymous", X.URL.isSameDomain() で切り替え + raw.loop = false; + raw.muted = false; + raw.crossorigin = option[ 'crossorigin' ] || ''; //crossorigin: "anonymous", X.URL.isSameDomain() で切り替え raw.style.cssText = 'position:absolute;bottom:0;left:-50px;width:100px;height:100px;opacity:0;'; raw.controls = false; raw.WebKitPlaysInline = true; raw.src = source; - //raw.onclick = "alert('play');this.play();"; + //raw.onclick = "alert('play');this.actualPlay();"; document.body.appendChild( raw ); //raw.load(); } else { @@ -159,6 +143,17 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ switch( e.type ){ case X_EVENT_KILL_INSTANCE : + // 【javascript】モバイル向けブラウザでも音を鳴らしたい【WebAudio】 + // http://ingaouhou.com/archives/3633 + // ・使い終わったインスタンスはload()しておくとやや安定 + this.playing && this.actualPause(); + delete this._closed; + delete this._loaded; + + this[ '_rawObject' ].src = ''; + this[ '_rawObject' ].load(); + + // removeChild for video break; }; }, @@ -170,7 +165,7 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ X_Audio_HTMLAudioWrapper_badOperaAndroid && alert( e.type ); - console.log( e.type ); + //console.log( e.type ); switch( e.type ){ case 'loadstart' : // ブラウザがコンテンツの検索を開始した場合に発生 @@ -230,11 +225,11 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ break; case 'ended' : - if( !this._closed && this.loop ){ - if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ + if( !this._closed && this.autoLoop ){ + if( !( this.target[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ this.looped = true; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); - this.play(); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.actualPlay(); }; return; }; @@ -250,11 +245,6 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ if( !this.duration && X_Type_isFinite( this[ '_rawObject' ].duration ) ){ this.duration = this.duration || this[ '_rawObject' ].duration * 1000; this._playForDuration = 2; - - //this.proxy[ 'asyncDispatch' ]( 'loadedmetadata' ); - //this.proxy[ 'asyncDispatch' ]( 'loadeddata' ); - //this.proxy[ 'asyncDispatch' ]( 'canplay' ); - //this.proxy[ 'asyncDispatch' ]( 'canplaythrough' ); loaded = true; //console.log( 'durationFix が完了' + this.duration ); break; @@ -264,8 +254,8 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ }; } else if( this[ '_rawObject' ].currentTime === this._lastCurrentTime ){ - //this.proxy[ 'dispatch' ]( 'seeking' ); - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_WAITING ); + //this.target[ 'dispatch' ]( 'seeking' ); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_WAITING ); return; }; this._lastCurrentTime = this[ '_rawObject' ].currentTime; @@ -274,17 +264,18 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ if( this.playing ){ end = X_AudioWrapper_getEndTime( this ); - now = X_Audio_HTMLAudioWrapper_currentTimeFix ? X_Timer_now() - this._playTime + this._beginTime : this[ '_rawObject' ].currentTime * 1000 | 0; + now = this.getActualCurrentTime(); + //console.log( end + ' / ' + now ); if( 0 + end <= 0 + now ){ // なぜか iem9 で必要,,, - if( this.loop ){ - if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ + if( this.autoLoop ){ + if( !( this.target[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ this.looped = true; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); - this.play(); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.actualPlay(); }; } else { - this.pause(); - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); + this.actualPause(); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); }; return; }; @@ -318,14 +309,7 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ console.log( '設定 ' + this._beginTime ); return; }; - - /* - this.proxy[ 'asyncDispatch' ]( 'loadedmetadata' ); - this.proxy[ 'asyncDispatch' ]( 'loadeddata' ); - this.proxy[ 'asyncDispatch' ]( 'canplay' ); - this.proxy[ 'asyncDispatch' ]( 'canplaythrough' ); */ - - + loaded = true; console.log( 'durationFix が完了' + this.duration ); @@ -348,27 +332,18 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ if( !this._loaded && ( loaded || e.type === X_Audio_HTMLAudio_playTrigger || e.type === 'loadeddata' ) ){ this.autoplay && X_Timer_once( 16, this, this.play ); this._loaded = true; - this.proxy[ 'dispatch' ]( X_EVENT_READY ); + this.target[ 'asyncDispatch' ]( X_EVENT_READY ); console.log( 'Loaded! ' + e.type + ' d:' + ( this.duration | 0 ) ); return; }; - loaded || ( type && this.proxy[ 'dispatch' ]( type ) ); - }, - - close : function(){ - // 【javascript】モバイル向けブラウザでも音を鳴らしたい【WebAudio】 - // http://ingaouhou.com/archives/3633 - // ・使い終わったインスタンスはload()しておくとやや安定 - this.playing && this.pause(); - delete this._closed; - delete this._loaded; - - this[ '_rawObject' ].src = ''; - this[ '_rawObject' ].load(); + if( !loaded && type ){ + this.target[ 'dispatch' ]( type ); + type === X_EVENT_ERROR && this[ 'kill' ](); + }; }, - - play : function(){ + + actualPlay : function(){ var begin, end; // もし kill 後に autoplayTimer で呼ばれても、_closed==true なので平気 @@ -397,14 +372,13 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ delete this._playForDuration; }; - if( !this.playing ){ if( X_UA[ 'Chrome' ] ){ // [CHROME][FIX] volume TODO どの version で 修正される? // [!] delay X_Timer_once( 0, this, this._fixForChrome ); this[ '_rawObject' ].volume = 0; } else { - this[ '_rawObject' ].volume = this.volume; + this[ '_rawObject' ].volume = this.gain; }; this[ '_rawObject' ].play(); this.playing = true; @@ -425,13 +399,13 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ // [CHROME][FIX] volume _fixForChrome : X_UA[ 'Chrome' ] && function(){ - !this._closed && ( this[ '_rawObject' ].volume = this.volume ); + !this._closed && ( this[ '_rawObject' ].volume = this.gain ); }, - pause : function(){ + actualPause : function(){ if( !this.playing ) return; - this.seekTime = this.state().currentTime; + this.seekTime = this.getActualCurrentTime(); delete this._playTime; @@ -443,55 +417,30 @@ if( X_Audio_constructor && !X_Audio_HTMLAudioWrapper_badOperaAndroid ){ }; delete this.playing; }, - - state : function( obj ){ - var result; - - if( obj === undefined ){ - return { - startTime : this.startTime, - endTime : this.endTime < 0 ? this.duration : this.endTime, - loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime, - loopEndTime : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime, - - loop : this.loop, - looped : this.looped, - volume : this.volume, - playing : this.playing, // && !this[ '_rawObject' ].error && !this[ '_rawObject' ].paused && !this[ '_rawObject' ].ended, - duration : this.duration, - - currentTime : - this.playing ? - ( X_Audio_HTMLAudioWrapper_currentTimeFix ? + + getActualCurrentTime : function(){ + return ( X_Audio_HTMLAudioWrapper_currentTimeFix ? X_Timer_now() - this._playTime + this._beginTime : - this[ '_rawObject' ].currentTime * 1000 | 0 ) : - this.seekTime, - /* - http://www.w3schools.com/tags/av_prop_error.asp - 1 = MEDIA_ERR_ABORTED - fetching process aborted by user - 2 = MEDIA_ERR_NETWORK - error occurred when downloading - 3 = MEDIA_ERR_DECODE - error occurred when decoding - 4 = MEDIA_ERR_SRC_NOT_SUPPORTED - audio/video not supported - */ - error : this[ '_rawObject' ].error || 0 // 0, 1 ~ 4 - }; - }; + this[ '_rawObject' ].currentTime * 1000 | 0 ); + }, + /* + http://www.w3schools.com/tags/av_prop_error.asp + 1 = MEDIA_ERR_ABORTED - fetching process aborted by user + 2 = MEDIA_ERR_NETWORK - error occurred when downloading + 3 = MEDIA_ERR_DECODE - error occurred when decoding + 4 = MEDIA_ERR_SRC_NOT_SUPPORTED - audio/video not supported + */ + getActualError : function(){ + return this[ '_rawObject' ].error || 0; + }, - result = X_AudioWrapper_updateStates( this, obj ); - - if( result & 2 ){ // seek - this.play(); - //} else - //if( result & 1 ){ - //if( X_Audio_HTMLAudioWrapper_currentTimeFix ){ - // this.play(); - //}; - + afterUpdateState : function( result ){ + if( result & 3 ){ // seek + this.actualPlay(); } else if( result & 4 ){ - this[ '_rawObject' ].volume = this.volume; - }; - + this[ '_rawObject' ].volume = this.gain; + }; } } diff --git a/0.6.x/js/07_audio/03_XSilverlightAudio.js b/0.6.x/js/07_audio/03_XSilverlightAudio.js index f4b84ab..fd9f694 100644 --- a/0.6.x/js/07_audio/03_XSilverlightAudio.js +++ b/0.6.x/js/07_audio/03_XSilverlightAudio.js @@ -18,27 +18,12 @@ var X_Audio_SLAudioWrapper, if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ // X.Node.inherits はできない。_rawObject は でなく silverlight - X_Audio_SLAudioWrapper = X_EventDispatcher[ 'inherits' ]( + X_Audio_SLAudioWrapper = X_Audio_AbstractAudioBackend[ 'inherits' ]( 'X.AV.SilverlightAudioWrapper', X_Class.POOL_OBJECT, { - '_rawType' : X_EventDispatcher_EVENT_TARGET_TYPE.SILVER_LIGHT, - proxy : null, - - startTime : 0, - endTime : -1, - loopStartTime : -1, - loopEndTime : -1, - seekTime : -1, - duration : 0, - - playing : false, - error : 0, - loop : false, - looped : false, - autoplay : false, - volume : 0.5, - + '_rawType' : X_EventDispatcher_EVENT_TARGET_SILVER_LIGHT, + _onload : '', _callback : null, xnodeObject : null, @@ -49,13 +34,13 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ _lastState : '', _interval : 0, // setInterval timer id - Constructor : function( proxy, source, option ){ + Constructor : function( target, source, option ){ + var xnodeScript; if( !X_Audio_SLAudio_uid ){ // source - // X_Node_systemNode[ 'create' ]( 'script', { type : 'text/xaml', id : 'silverlightaudio' } ) - // [ 'text' ]( ''); + //xnodeScript = X_Node_head[ 'create' ]( 'script', { type : 'text/xaml', id : 'silverlightaudio' } ); + //xnodeScript[ '_rawObject' ].innerHTML = ''; // html に以下を書いた // @@ -67,7 +52,7 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ */ // TODO embed - this.proxy = proxy; + this.target = target || this; this._source = source; // X.Audio._slOnload_ は不可 this._onload = 'XAudioSilverlightOnLoad' + ( ++X_Audio_SLAudio_uid ); @@ -88,31 +73,29 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ //'' + //'' // bond to global ); - X_AudioWrapper_updateStates( this, option ); + this.setState( option ); this[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE ); }, - onSLReady : function( sender ){ - if( !this._onload ) return; - - window[ this._onload ] = null; - delete this._onload; - X_Callback_correct( this._callback ); - delete this._callback; - - sender.children.add( - sender.GetHost(). - content. - CreateFromXaml( - '' + - '' + - '')); - - this[ '_rawObject' ] = sender.findName('media'); // x:Name='media' - - this[ 'listen' ]( [ 'MediaFailed', 'MediaOpened', 'MediaEnded', 'CurrentStateChanged' ] ); - }, + onSLReady : function( sender ){ + if( !this._onload ) return; + + window[ this._onload ] = null; + delete this._onload; + X_Callback_correct( this._callback ); + delete this._callback; + + sender[ 'children' ][ 'add' ]( + sender[ 'GetHost' ]()[ 'content' ][ 'CreateFromXaml' ]( + '' + + '' + + '')); + + this[ '_rawObject' ] = sender[ 'findName' ]( 'media' ); // x:Name='media' + + this[ 'listen' ]( [ 'MediaFailed', 'MediaOpened', 'MediaEnded', 'CurrentStateChanged' ] ); + }, handleEvent : function( e ){ var lastState, currentState; @@ -125,30 +108,31 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ this.playing = false; this._ended = true; this._paused = false; - this.proxy[ 'dispatch' ]( X_EVENT_ERROR ); // open failed + if( this.playing ){ + //X_Timer_once( 16, this, this.actualPlay ); + } else { + this.target[ 'dispatch' ]( X_EVENT_ERROR ); // open failed + this[ 'kill' ](); + }; break; case 'MediaOpened' : // http://msdn.microsoft.com/ja-jp/library/bb979710(VS.95).aspx - this.duration = this[ '_rawObject' ].NaturalDuration.Seconds * 1000; - // TODO 'canplaythrough' - //this.proxy[ 'asyncDispatch' ]( 'loadstart' ); - //this.proxy[ 'asyncDispatch' ]( 'loadedmetadata' ); - //this.proxy[ 'asyncDispatch' ]( 'loadeddata' ); - //this.proxy[ 'asyncDispatch' ]( 'canplay' ); - //this.proxy[ 'asyncDispatch' ]( 'canplaythrough' ); - this.proxy[ 'asyncDispatch' ]( X_EVENT_READY ); + this.duration = this[ '_rawObject' ][ 'NaturalDuration' ][ 'Seconds' ] * 1000; + this.target[ 'asyncDispatch' ]( X_EVENT_READY ); - this.autoplay && X_Timer_once( 16, this, this.play ); + this.autoplay && X_Timer_once( 16, this, this.actualPlay ); break; - case 'MediaEnded' : - this.loop && this.playing && this.play(); + case 'MediaEnded' : + //console.log( ' > ' + this.autoLoop + ' error:' + this.error ); + //this.autoLoop && /* this.playing && */ this.actualPlay(); + this._ended = true; break; case 'CurrentStateChanged' : lastState = this._lastState, - currentState = this[ '_rawObject' ].CurrentState; + currentState = this[ '_rawObject' ][ 'CurrentState' ]; // ignore consecutive events or 'Closed' == 'Error' if( lastState === currentState @@ -157,15 +141,17 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ }; this._lastState = currentState; // update last state + console.log( ' > ' + currentState + ' - ' + this._lastUserAction ); + switch( currentState ){ case 'Buffering' : case 'Opening' : switch( this._lastUserAction ){ case 'play' : - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_WAITING ); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_WAITING ); break; case 'seek' : - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_SEEKING ); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_SEEKING ); break; case 'pause' : break; @@ -182,23 +168,26 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ this.playing = false; this._ended = true; this._paused = false; - this.proxy[ 'dispatch' ]( X_EVENT_ERROR ); + this.target[ 'dispatch' ]( X_EVENT_ERROR ); + this[ 'kill' ](); break; // userAction.pause() -> MediaState('Paused') -> x // userAction.stop() -> MediaState('Paused') -> x // userAction.play() + file end -> MediaState('Paused') -> uueventfire('ended') case 'Paused': - this.playing = false; + + this.playing && X_Timer_once( 16, this, this.actualPlay ); + //this.playing = false; switch( this._lastUserAction ){ case 'play': // play() -> file end -> event('ended') case 'seek': - this.seekTime = 0; + //this.seekTime = 0; this._ended = true; this._paused = false; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); - this._currentTime( this.startTime ); + //this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); + //this.setCurrentTime( this.startTime ); break; case 'pause': this._ended = false; @@ -213,23 +202,29 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ // media.play -> 'Playing' case 'Playing': this.error = 0; - this.playing = true; + //this.playing = true; this._ended = false; this._paused = false; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); break; // stop() case 'Stopped': - this.playing = false; + this.playing && X_Timer_once( 16, this, this.actualPlay ); + return; + + //this.playing = false; this._ended = true; this._paused = false; - this._currentTime( this.startTime ); + //this.setCurrentTime( this.startTime ); break; }; break; case X_EVENT_KILL_INSTANCE : + this.playing && this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); + this.playing && this.actualPause(); + if( this._onload ){ // window への delete に ie5 は対応しないが、そもそも ie5 は Silverlight に非対応 window[ this._onload ] = null; @@ -241,16 +236,10 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ }; }, - close : function(){ - this.playing && this.pause(); - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); - this[ 'kill' ](); - }, - // SilverlightAudio.play - play : function(){ - var begin, end; - + actualPlay : function(){ + var begin, offset, end; + // もし kill 後に autoplayTimer で呼ばれても、_closed==true なので平気 if( this.error ) return; if( !this.duration ){ @@ -262,27 +251,33 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ end = X_AudioWrapper_getEndTime( this ); begin = X_AudioWrapper_getStartTime( this, end, true ) | 0; - - console.log( '[SLAudio] play ' + begin + ' -> ' + end ); - - this[ '_rawObject' ].Volume = this.volume; - this._beginTime = begin; - this._currentTime( begin ); + + // 1 秒以下は指定できないため四捨五入 + begin = ( begin / 1000 | 0 ) * 1000 + ( 500 < begin % 1000 ? 1000 : 0 ); + + this[ '_rawObject' ][ 'Volume' ] = this.gain; - if( !this.playing ){ + this.setCurrentTime( this._beginTime = begin ); + + console.log( '[play] ' + begin + ' -> ' + end ); + + /* + if( offset = begin - this.getActualCurrentTime() ){ + this.setCurrentTime( begin + offset ); + console.log( ' [差補正] ' + offset + ' ct:' + this.getActualCurrentTime() + ' begin:' + begin ); + this._beginTime = begin = this.getActualCurrentTime(); + };*/ + + if( !this.playing || this._ended ){ + console.log( '[play] play()' + begin + ' -> ' + end ); this[ '_rawObject' ].play(); - //this.proxy[ 'dispatch' ]( 'play' ); - this.playing = true; + this._ended = false; }; this._timerID && X_Timer_remove( this._timerID ); - if( end < this.duration ){ - this._timerID = X_Timer_once( end - begin, this, this._onEnded ); - } else { - delete this._timerID; - }; + this._timerID = X_Timer_once( end - begin, this, this._onEnded ); if( !this._interval ){ this._interval = X_Timer_add( 1000, 0, this, this._onInterval ); @@ -294,7 +289,7 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ delete this._interval; return X_Callback_UN_LISTEN; }; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_PLAYING ); }, _onEnded : function(){ @@ -302,103 +297,91 @@ if( X[ 'Pulgin' ][ 'SilverlightEnabled' ] ){ delete this._timerID; if( this.playing ){ - console.log( '> end ' + X_AudioWrapper_getEndTime( this ) + ' current:' + ( this[ '_rawObject' ].Position.Seconds * 1000 | 0 ) ); - time = this[ '_rawObject' ].Position.Seconds * 1000 | 0; + //console.log( '> end ' + X_AudioWrapper_getEndTime( this ) + ' current:' + ( this.getActualCurrentTime() ) ); + time = this.getActualCurrentTime(); - if( time <= this._beginTime ){ - console.log( '== waiting' ); - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_WAITING ); - this._timerID = X_Timer_once( X_AudioWrapper_getEndTime( this ) - this._beginTime, this, this._onEnded ); + if( time < this._beginTime ){ + console.log( '== waiting ' + time + ' < begin:' + this._beginTime ); + this.setCurrentTime( this._beginTime ); + time = this.getActualCurrentTime(); + console.log( ' > ' + time ); + this._ended && this[ '_rawObject' ].play(); + this._ended = false; + this.target[ 'dispatch' ]( X_EVENT_MEDIA_WAITING ); + this._timerID = X_Timer_once( X_AudioWrapper_getEndTime( this ) - time, this, this._onEnded ); return; }; time -= X_AudioWrapper_getEndTime( this ); if( time < 0 ){ - console.log( '> onEnd ' + time ); + console.log( ' > まだ終わらない ' + time ); + this._ended && this[ '_rawObject' ].play(); + this._ended = false; this._timerID = X_Timer_once( -time, this, this._onEnded ); return; }; - if( this.loop ){ - if( !( this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ + if( this.autoLoop ){ + console.log( '========= loop?' ); + if( !( this.target[ 'dispatch' ]( X_EVENT_MEDIA_BEFORE_LOOP ) & X_Callback_PREVENT_DEFAULT ) ){ + console.log( '========== loopした' ); this.looped = true; - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); - this.play(); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_LOOPED ); + this.actualPlay(); }; } else { - this.pause(); - this.proxy[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); + console.log( '========= pause' ); + this.actualPause(); + this.target[ 'dispatch' ]( X_EVENT_MEDIA_ENDED ); }; }; }, // SilverlightAudio.pause - pause : function(){ + actualPause : function(){ if( this.error || !this.playing ) return; this._lastUserAction = 'pause'; - this.seekTime = this.state().currentTime; + this.seekTime = this.getActualCurrentTime(); this.playing = false; this._paused = true; this._ended = false; - + this[ '_rawObject' ].pause(); - //this.proxy[ 'dispatch' ]( 'pause' ); + //this.target[ 'dispatch' ]( 'pause' ); }, - - // SilverlightAudio.state - state : function( obj ){ // @return Hash: { loop, error, paused, ended, source, duration } - var result, end; - - if( obj === undefined ){ - return { - startTime : this.startTime, - endTime : this.endTime < 0 ? this.duration : this.endTime, - loopStartTime : this.loopStartTime < 0 ? this.startTime : this.loopStartTime, - loopEndTime : this.loopEndTime < 0 ? ( this.endTime || this.duration ) : this.loopEndTime, - - // 整数化 しておかないと seek 時に不具合がある。 - currentTime : this.playing ? this[ '_rawObject' ].Position.Seconds * 1000 | 0 : this.seekTime, - loop : this.loop, - looped : this.looped, - volume : this.volume, - error : this.error, - playing : this.playing, - duration : this.duration // this[ '_rawObject' ].NaturalDuration.Seconds; - }; - }; + getActualCurrentTime : function(){ + return this[ '_rawObject' ][ 'Position' ][ 'Seconds' ] * 1000 | 0; + }, - result = X_AudioWrapper_updateStates( this, obj ); - - if( result & 2 ){ // seek - this.play(); - } else { - if( result & 1 ){ - end = X_AudioWrapper_getEndTime( this ); - halfway = end < this.duration; - this._timerID && X_Timer_remove( this._timerID ); - - if( halfway ){ - this._timerID = X_Timer_once( end - this[ '_rawObject' ].Position.Seconds * 1000 | 0, this, this._onEnded ); - } else { - delete this._timerID; - }; - - }; - if( result & 4 ){ - this[ '_rawObject' ].Volume = this.volume; + afterUpdateState : function( result ){ + if( result & 3 ){ // seek + this.actualPlay(); + } else + if( result & 1 ){ + end = X_AudioWrapper_getEndTime( this ); + halfway = end < this.duration; + this._timerID && X_Timer_remove( this._timerID ); + + if( halfway ){ + this._timerID = X_Timer_once( end - this.getActualCurrentTime(), this, this._onEnded ); + } else { + delete this._timerID; }; - }; + } else + if( result & 4 ){ + this[ '_rawObject' ][ 'Volume' ] = this.gain; + }; }, // SilverlightAudio.currentTime - _currentTime : function( time ){ // @param Number: time - var position = this[ '_rawObject' ].Position; // [!] create instance + setCurrentTime : function( time ){ // @param Number: time + var position = this[ '_rawObject' ][ 'Position' ]; // [!] create instance - position.Seconds = time / 1000 | 0; // set current time + position[ 'Seconds' ] = time / 1000 | 0; // set current time - this[ '_rawObject' ].Position = position; // [!] reattach instance + this[ '_rawObject' ][ 'Position' ] = position; // [!] reattach instance } } diff --git a/0.6.x/js/07_audio/10_XAudioSprite.js b/0.6.x/js/07_audio/10_XAudioSprite.js index 99d7782..45790ae 100644 --- a/0.6.x/js/07_audio/10_XAudioSprite.js +++ b/0.6.x/js/07_audio/10_XAudioSprite.js @@ -27,30 +27,63 @@ var X_Audio_Sprite_shouldUse = window.HTMLAudioElement && ( X_UA[ 'iOS' ] bgmLooped : false, bgmPlaying : false }, - X_Audio_Sprite_instance; + X_Audio_Sprite_instance, + X_Audio_Sprite_numTracks, + X_Audio_Sprite_useVideo; -X[ 'Audio' ][ 'Sprite' ] = { - - 'shouldUse' : X_Audio_Sprite_shouldUse, +X[ 'AudioSprite' ] = function( setting ){ + var tracks = X_Audio_Sprite_TEMP.tracks, + bgms = X_Audio_Sprite_TEMP.BGMs, + presets = X_Audio_Sprite_TEMP.presets, + urls = setting[ 'urls' ], + video = setting[ 'useVideo' ], + n = video ? 1 : setting[ 'numTracks' ] || 1, + option = { + volume : setting[ 'volume' ] || 0.5, + autoplay : false, + startTime : 0, + endTime : X_Audio_Sprite_lengthSilence, + loop : true + }, + k, i, v, track; - 'needTouchFirst' : X_Audio_Sprite_needTouchFirst, + if( X_Audio_Sprite_instance ){ + X_Audio_Sprite_instance[ 'kill' ](); + } else { + X_Audio_Sprite_instance = X_Class_override( X_EventDispatcher(), X_Audio_Sprite_members ); + X_ViewPort[ 'listen' ]( [ X_EVENT_VIEW_ACTIVATE, X_EVENT_VIEW_DEACTIVATE ], X_Audio_Sprite_instance, X_Audio_Sprite_handleEvent ); + }; - 'enableMultiTrack' : X_Audio_Sprite_enableMultiTrack, + n = n <= X_Audio_Sprite_maxTracks ? n : X_Audio_Sprite_maxTracks; - 'create' : function( setting ){ - // close() - if( X_Audio_Sprite_instance ){ - X_Audio_Sprite_instance.close(); - } else { - X_Audio_Sprite_instance = X_Class_override( X_EventDispatcher(), X_Audio_Sprite_members ); - X_ViewPort[ 'listen' ]( [ X_EVENT_VIEW_ACTIVATE, X_EVENT_VIEW_DEACTIVATE ], X_Audio_Sprite_instance, X_Audio_Sprite_handleEvent ); + for( k in setting ){ + v = setting[ k ]; + if( X_Type_isArray( v ) && v !== urls ){ + v = X_Object_cloneArray( v ); + for( i = v.length; i; ){ + --i; + if( i !== 2 ) v[ i ] = X_AudioWrapper_timeStringToNumber( v[ i ] ); + }; + if( v[ 2 ] ) bgms[ k ] = v; + presets[ k ] = v; }; - X_Audio_Sprite_instance.setup( setting ); - return X_Audio_Sprite_instance; - - } + }; + + X_Audio_startDetectionBackend( X_Audio_BACKENDS[ 0 ], X_Audio_Sprite_instance, X_Object_cloneArray( urls ), option ); + + X_Audio_Sprite_instance[ 'listenOnce' ]( [ X_EVENT_BACKEND_READY, X_EVENT_BACKEND_NONE ], X_AudioSprite_backendHandler ); + X_Audio_Sprite_instance[ 'listenOnce' ]( X_EVENT_KILL_INSTANCE, X_Audio_Sprite_handleEvent ); + + X_Audio_Sprite_useVideo = video; + X_Audio_Sprite_numTracks = X_Audio_Sprite_instance[ 'numTracks' ] = n; + + return X_Audio_Sprite_instance; }; +X[ 'AudioSprite' ][ 'shouldUse' ] = X_Audio_Sprite_shouldUse; +X[ 'AudioSprite' ][ 'needTouchFirst' ] = X_Audio_Sprite_needTouchFirst; +X[ 'AudioSprite' ][ 'enableMultiTrack' ] = X_Audio_Sprite_enableMultiTrack; + // 再生が終わっているもの、終わりかけのものを探す // TODO 終わりかけのもの、と一番古いもの、どちらを再利用するか?これ以上に細かい実装を望む場合は X.Audio.Sprite は使わず自力で実装 function X_Audio_Sprite_getTrackEnded(){ @@ -60,7 +93,7 @@ function X_Audio_Sprite_getTrackEnded(){ for( ; i < l; ++i ){ track = tracks[ i ]; - state = track.state(); + state = track.getState(); if( !state.playing ) return track; if( track === X_Audio_Sprite_TEMP.bgmTrack ) continue; if( state.currentTime <= X_Audio_Sprite_lengthSilence + X_Audio_Sprite_lengthDistance ) return track; @@ -94,84 +127,18 @@ function X_Audio_Sprite_getTrackEnded(){ X_Audio_Sprite_members = { - setup : function( setting ){ - - var tracks = X_Audio_Sprite_TEMP.tracks, - bgms = X_Audio_Sprite_TEMP.BGMs, - presets = X_Audio_Sprite_TEMP.presets, - urls = setting[ 'urls' ], - video = setting[ 'useVideo' ], - n = video ? 1 : setting[ 'numTracks' ] || 1, - option = { - volume : setting[ 'volume' ] || 0.5, - autoplay : false, - startTime : 0, - endTime : X_Audio_Sprite_lengthSilence, - loop : true - }, - k, i, v, track; - - n = n <= X_Audio_Sprite_maxTracks ? n : X_Audio_Sprite_maxTracks; - - for( k in setting ){ - v = setting[ k ]; - if( X_Type_isArray( v ) && v !== urls ){ - v = X_Object_cloneArray( v ); - for( i = v.length; i; ){ - --i; - if( i !== 2 ) v[ i ] = X_AudioWrapper_timeStringToNumber( v[ i ] ); - }; - if( v[ 2 ] ) bgms[ k ] = v; - presets[ k ] = v; - }; - }; - - for( i = 0; i < n; ++i ){ - if( video || ( i === 1 && X_Audio_Sprite_useVideoForMulti ) ){ - option[ 'useVideo' ] = true; - }; - tracks.push( X[ 'Audio' ]( urls, X_Object_clone( option ) ) ); - }; - - tracks[ n - 1 ][ 'listenOnce' ]( [ X_EVENT_BACKEND_READY, X_EVENT_BACKEND_NONE ], this, X_Audio_Sprite_handleEvent ); - - X_Audio_Sprite_instance.numTracks = n; - }, + 'numTracks' : 0, - close : function(){ - var tracks = X_Audio_Sprite_TEMP.tracks, - bgms = X_Audio_Sprite_TEMP.BGMs, - presets = X_Audio_Sprite_TEMP.presets, - k; - - while( tracks.length ){ - tracks.pop()[ 'kill' ](); - }; - - for( k in bgms ){ - delete bgms[ k ]; - }; - for( k in presets ){ - delete presets[ k ]; - }; - - X_Audio_Sprite_TEMP.bgmTrack = null; - X_Audio_Sprite_TEMP.bgmPosition = 0; - X_Audio_Sprite_TEMP.bgmName = ''; - X_Audio_Sprite_TEMP.bgmLooped = false; - X_Audio_Sprite_TEMP.bgmPlaying = false; - }, - - load : function(){ + 'load' : function(){ var tracks = X_Audio_Sprite_TEMP.tracks, i = 0, l = tracks.length; for( ; i < l; ++i ){ if( X_UA[ 'WinPhone' ] ){ console.log( 'touch -> play()' ); //tracks[ i ].play( 0, X_Audio_Sprite_lengthSilence, true, 0, X_Audio_Sprite_lengthSilence ).seek( 0 ); - this.pause( i ); + this[ 'pause' ]( i ); } else { - X_Audio_getAudioWrapper( tracks[ i ] )[ '_rawObject' ].load(); + tracks[ i ][ '_rawObject' ].load(); }; }; }, @@ -179,7 +146,7 @@ X_Audio_Sprite_members = { /* * @return uid Number */ - play : function( name ){ + 'play' : function( name ){ var bgm = X_Audio_Sprite_TEMP.bgmTrack, tracks = X_Audio_Sprite_TEMP.tracks, bgms = X_Audio_Sprite_TEMP.BGMs, @@ -193,9 +160,11 @@ X_Audio_Sprite_members = { // bgm変更 X_Audio_Sprite_TEMP.bgmName = name; X_Audio_Sprite_TEMP.bgmPosition = preset[ 0 ]; - X_Audio_Sprite_TEMP.bgmPlaying = true; X_Audio_Sprite_TEMP.bgmLooped = false; }; + + X_Audio_Sprite_TEMP.bgmPlaying = true; + if( bgm ){ track = bgm; } else @@ -205,22 +174,20 @@ X_Audio_Sprite_members = { track = X_Audio_Sprite_TEMP.bgmTrack = tracks[ 0 ]; }; - if( track[ 'listen' ]( [ X_EVENT_MEDIA_PLAYING, X_EVENT_MEDIA_BEFORE_LOOP ], this, X_Audio_Sprite_handleEvent ).isPlaying() ){ - track - .state( { - loop : true, - looped : X_Audio_Sprite_TEMP.bgmLooped, - currentTime : X_Audio_Sprite_TEMP.bgmPosition, - startTime : preset[ 0 ], - endTime : preset[ 1 ], - loopStartTime : preset[ 3 ], - loopEndTime : preset[ 4 ] - } ); + if( track[ 'listen' ]( [ X_EVENT_MEDIA_PLAYING, X_EVENT_MEDIA_BEFORE_LOOP ], this, X_Audio_Sprite_handleEvent ).playing ){ + track.setState({ + 'loop' : true, + 'looped' : X_Audio_Sprite_TEMP.bgmLooped, + 'currentTime' : X_Audio_Sprite_TEMP.bgmPosition, + 'startTime' : preset[ 0 ], + 'endTime' : preset[ 1 ], + 'loopStartTime' : preset[ 3 ], + 'loopEndTime' : preset[ 4 ] + }); } else { - track - .state( { looped : X_Audio_Sprite_TEMP.bgmLooped } ) - .play( preset[ 0 ], preset[ 1 ], true, preset[ 3 ], preset[ 4 ] ) - .seek( X_Audio_Sprite_TEMP.bgmPosition ); + track.setState( { 'looped' : X_Audio_Sprite_TEMP.bgmLooped } ); + track.play( preset[ 0 ], preset[ 1 ], true, preset[ 3 ], preset[ 4 ] ); + track.seek( X_Audio_Sprite_TEMP.bgmPosition ); }; } else { @@ -228,30 +195,29 @@ X_Audio_Sprite_members = { track = X_Audio_Sprite_getTrackEnded( X_Audio_Sprite_TEMP.bgmPlaying ); track [ 'listen' ]( [ X_EVENT_MEDIA_PLAYING, X_EVENT_MEDIA_BEFORE_LOOP ], this, X_Audio_Sprite_handleEvent ) - .state( { looped : false } ) - .play( preset[ 0 ], preset[ 1 ], true, 0, X_Audio_Sprite_lengthSilence ); + .setState( { 'looped' : false } ); + track.play( preset[ 0 ], preset[ 1 ], true, 0, X_Audio_Sprite_lengthSilence ); } else { // single track, iOS if( bgm ){ X_Audio_Sprite_TEMP.bgmPosition = bgm.currentTime(); - console.log( 'bgm position : ' + X_Audio_Sprite_TEMP.bgmPosition + ' isPlay:' + bgm.isPlaying() ); + //console.log( 'bgm position : ' + X_Audio_Sprite_TEMP.bgmPosition + ' isPlay:' + bgm.playing ); X_Audio_Sprite_TEMP.bgmTrack = null; }; track = tracks[ 0 ]; - if( track[ 'listen' ]( [ X_EVENT_MEDIA_PLAYING, X_EVENT_MEDIA_BEFORE_LOOP ], this, X_Audio_Sprite_handleEvent ).isPlaying() ){ - track - .state( { - loop : true, - looped : false, - startTime : preset[ 0 ], - endTime : preset[ 1 ], - loopStartTime : 0, - loopEndTime : X_Audio_Sprite_lengthSilence - } ); + if( track[ 'listen' ]( [ X_EVENT_MEDIA_PLAYING, X_EVENT_MEDIA_BEFORE_LOOP ], this, X_Audio_Sprite_handleEvent ).playing ){ + track.setState({ + 'loop' : true, + 'looped' : false, + //'currentTime' : preset[ 0 ], + 'startTime' : preset[ 0 ], + 'endTime' : preset[ 1 ], + 'loopStartTime' : 0, + 'loopEndTime' : X_Audio_Sprite_lengthSilence + }); } else { - track - .play( preset[ 0 ], preset[ 1 ], true, 0, X_Audio_Sprite_lengthSilence ); + track.play( preset[ 0 ], preset[ 1 ], true, 0, X_Audio_Sprite_lengthSilence ); }; }; }; @@ -260,19 +226,20 @@ X_Audio_Sprite_members = { return -1; }, - pause : function( uid ){ + 'pause' : function( uid ){ var track = X_Audio_Sprite_TEMP.tracks[ uid ]; if( X_Audio_Sprite_TEMP.bgmTrack === track ){ X_Audio_Sprite_TEMP.bgmPosition = track.currentTime(); X_Audio_Sprite_TEMP.bgmPlaying = false; X_Audio_Sprite_TEMP.bgmTrack = null; }; - track && track.play( 0, X_Audio_Sprite_lengthSilence, true, 0, X_Audio_Sprite_lengthSilence ).seek( 0 ); + track && track.play( 0, X_Audio_Sprite_lengthSilence, true, 0, X_Audio_Sprite_lengthSilence ); + track && track.seek( 0 ); this[ 'asyncDispatch' ]( X_EVENT_MEDIA_PAUSED ); return this; }, - seek : function( uid, position ){ + 'seek' : function( uid, position ){ var track = X_Audio_Sprite_TEMP.tracks[ uid ], end; if( track ){ @@ -283,7 +250,7 @@ X_Audio_Sprite_members = { return this; }, - volume : function( uid, opt_volume ){ + 'volume' : function( uid, opt_volume ){ var track, i; // TODO uid = 0 if( uid === 0 ){ @@ -297,48 +264,64 @@ X_Audio_Sprite_members = { }; track = X_Audio_Sprite_TEMP.tracks[ uid ]; if( opt_volume === undefined ){ - return track ? track.volume() : -1; + return track ? track.gain : -1; }; track && track.volume( opt_volume ); return this; }, - state : function( uid, opt_obj ){ + 'state' : function( uid, opt_obj ){ var track = X_Audio_Sprite_TEMP.tracks[ uid ], state, start, end; // TODO uid = 0 if( opt_obj === undefined ){ // TODO pause if( track ){ - state = track.state(); + state = track.getState(); start = state.startTime; return { - 'currentTime' : state.currentTime - state.startTime, - 'playing' : state.startTime <= state.currentTime && state.currentTime <= state.endTime, - 'duration' : state.endTime - state.startTime, + 'currentTime' : state.currentTime - start, + 'playing' : start <= state.currentTime && state.currentTime <= state.endTime, + 'duration' : state.endTime - start, 'volume' : X_Audio_Sprite_TEMP.volume }; }; return { 'volume' : X_Audio_Sprite_TEMP.volume, 'playing' : false }; }; - track && track.state( opt_obj ); + track && track.setState( opt_obj ); return this; } }; -function X_Audio_Sprite_handleEvent( e ){ - var i, tracks, track, _e; +function X_AudioSprite_backendHandler( e ){ + var i, backend, option, src, name, last, _e; switch( e.type ){ case X_EVENT_BACKEND_READY : + + backend = X_Audio_BACKENDS[ e[ 'backendID' ] ]; + option = e[ 'option' ]; + + this[ 'unlisten' ]( X_EVENT_BACKEND_NONE, X_AudioSprite_backendHandler ); + this[ 'source' ] = src = e[ 'source' ]; + this[ 'backendName' ] = name = backend.backendName; + + for( i = 0; i < X_Audio_Sprite_numTracks; ++i ){ + if( X_Audio_Sprite_useVideo || ( i === 1 && X_Audio_Sprite_useVideoForMulti ) ){ + option[ 'useVideo' ] = true; + }; + // Audiobackend の owner として null を渡すとAudioBackend 自身へ dispatch する + X_Audio_Sprite_TEMP.tracks.push( last = backend.klass( null, e[ 'source' ], option ) ); + }; + _e = { 'type' : X_EVENT_BACKEND_READY, - 'source' : e[ 'source' ], - 'backendName' : e[ 'backendName' ] + 'source' : src, + 'backendName' : name }; if( X_Audio_Sprite_needTouchFirst ){ - if( e.backendName === 'Web Audio' ){ + if( name === 'Web Audio' ){ _e[ 'needTouchForPlay' ] = true; } else { _e[ 'needTouchForLoad' ] = true; @@ -346,37 +329,46 @@ function X_Audio_Sprite_handleEvent( e ){ }; this[ 'asyncDispatch' ]( _e ); - e.target - [ 'unlisten' ]( X_EVENT_BACKEND_NONE, this, X_Audio_Sprite_handleEvent ) - [ 'listenOnce' ]( X_EVENT_READY, this, X_Audio_Sprite_handleEvent ); + last[ 'listenOnce' ]( X_EVENT_READY, this, X_AudioSprite_backendHandler ); // READY, needTouchForPlay, needTouchForLoad if( X_Audio_HTMLAudioWrapper_durationFix ){ for( i = 0; i < X_Audio_Sprite_TEMP.tracks.length; ++i ){ - X_Audio_Sprite_instance.pause( i ); + this[ 'pause' ]( i ); }; }; + + return X_Callback_STOP_NOW; break; case X_EVENT_BACKEND_NONE : - this[ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); - e.target[ 'unlisten' ]( X_EVENT_BACKEND_READY, this, X_Audio_Sprite_handleEvent ); + this[ 'unlisten' ]( X_EVENT_BACKEND_READY, this, X_AudioSprite_backendHandler ) + [ 'asyncDispatch' ]( X_EVENT_BACKEND_NONE ); + return X_Callback_STOP_NOW; break; case X_EVENT_READY : console.log( 'X.AudioSprite - Ready!' ); + if( X_Audio_Sprite_needTouchAndroid ){ for( i = 0; i < X_Audio_Sprite_TEMP.tracks.length; ++i ){ - X_Audio_Sprite_instance.pause( i ); + this[ 'pause' ]( i ); }; e.target[ 'listenOnce' ]( X_EVENT_MEDIA_PLAYING, this, this.asyncDispatch, [ X_EVENT_READY ] ); // Android 標準ブラウザ return; }; this[ 'asyncDispatch' ]( X_EVENT_READY ); break; - + }; +}; + + +function X_Audio_Sprite_handleEvent( e ){ + var i, tracks, track, _e, k; + + switch( e.type ){ case X_EVENT_MEDIA_PLAYING : - ( e.target === X_Audio_Sprite_TEMP.bgmTrack || !e.target.state().looped ) && this[ 'asyncDispatch' ]( X_EVENT_MEDIA_PLAYING ); + ( e.target === X_Audio_Sprite_TEMP.bgmTrack || !e.target.looped ) && this[ 'asyncDispatch' ]( X_EVENT_MEDIA_PLAYING ); break; case X_EVENT_MEDIA_BEFORE_LOOP : @@ -384,12 +376,14 @@ function X_Audio_Sprite_handleEvent( e ){ X_Audio_Sprite_TEMP.bgmLooped = true; this[ 'asyncDispatch' ]( X_EVENT_MEDIA_LOOPED ); // TODO uid } else { - if( e.target.state().looped ){ + if( e.target.looped ){ //this[ 'asyncDispatch' ]( X_EVENT_MEDIA_LOOPED ); // TODO uid } else { this[ 'asyncDispatch' ]( X_EVENT_MEDIA_ENDED ); // TODO uid }; + console.log( '[AudioSprite] ' + X_Audio_Sprite_TEMP.bgmPlaying + ' ' + !X_Audio_Sprite_TEMP.bgmTrack ); + // single track | iOS if( X_Audio_Sprite_TEMP.bgmPlaying && !X_Audio_Sprite_TEMP.bgmTrack ){ X_Audio_Sprite_TEMP.bgmTrack = e.target; @@ -399,11 +393,12 @@ function X_Audio_Sprite_handleEvent( e ){ }; break; + // TODO Android Firefox で アクティブ検出できない! case X_EVENT_VIEW_ACTIVATE : console.log( '■ アクティブ' ); // track.play(); or iOS need touch?? tracks = X_Audio_Sprite_TEMP.pauseTracks; - while( tracks.length ) tracks.pop().play(); + while( tracks.length ) tracks.pop().actualPlay(); break; case X_EVENT_VIEW_DEACTIVATE : @@ -413,13 +408,30 @@ function X_Audio_Sprite_handleEvent( e ){ i = tracks.length; for( ; i; ){ track = tracks[ --i ]; - track.isPlaying() && X_Audio_Sprite_TEMP.pauseTracks.push( track.pause() ); + track.playing && X_Audio_Sprite_TEMP.pauseTracks.push( track ) && track.pause(); }; break; case X_EVENT_KILL_INSTANCE : + + while( X_Audio_Sprite_TEMP.tracks.length ){ + X_Audio_Sprite_TEMP.tracks.pop()[ 'kill' ](); + }; + + for( k in X_Audio_Sprite_TEMP.bgms ){ + delete X_Audio_Sprite_TEMP.bgms[ k ]; + }; + for( k in X_Audio_Sprite_TEMP.presets ){ + delete X_Audio_Sprite_TEMP.presets[ k ]; + }; + + X_Audio_Sprite_TEMP.bgmTrack = null; + X_Audio_Sprite_TEMP.bgmPosition = 0; + X_Audio_Sprite_TEMP.bgmName = ''; + X_Audio_Sprite_TEMP.bgmLooped = false; + X_Audio_Sprite_TEMP.bgmPlaying = false; + X_ViewPort[ 'unlisten' ]( [ X_EVENT_VIEW_ACTIVATE, X_EVENT_VIEW_DEACTIVATE ], this, X_Audio_Sprite_handleEvent ); - this.close(); break; }; }; diff --git a/0.6.x/js/20_ui/00_XUI.js b/0.6.x/js/20_ui/00_XUI.js index 3f03b2a..f856e85 100644 --- a/0.6.x/js/20_ui/00_XUI.js +++ b/0.6.x/js/20_ui/00_XUI.js @@ -16,27 +16,3 @@ X.UI = { currentRootData : null }; - -/* - * 'none,chrome,container' を受け取ったら、 - * { - * 'none' : 1, - * 'chrome' : 2, - * 'container' : 3, - * 1 : 'none', - * 2 : 'chrome', - * 3 : 'container' - * } こんな object を返す。 - */ -function XUI_createChecker( str ){ - var ret = {}, - ary = str.split( ',' ), - l = ary.length, - i = 0, v; - for( ; i < l; ){ - v = ary[ i ]; - ret[ v ] = ++i; - ret[ i ] = v; - }; - return ret; -}; diff --git a/0.6.x/js/20_ui/02_XUI_Attr.js b/0.6.x/js/20_ui/02_XUI_Attr.js index 4908ed3..ff7356e 100644 --- a/0.6.x/js/20_ui/02_XUI_Attr.js +++ b/0.6.x/js/20_ui/02_XUI_Attr.js @@ -117,6 +117,29 @@ XUI_attrClassProto = null, XUI_AttrClass = X_Class_create( 'XUI_AttrClass', X_Class.POOL_OBJECT ); +/* + * 'none,chrome,container' を受け取ったら、 + * { + * 'none' : 1, + * 'chrome' : 2, + * 'container' : 3, + * 1 : 'none', + * 2 : 'chrome', + * 3 : 'container' + * } こんな object を返す。 + */ +function XUI_createChecker( str ){ + var ret = {}, + ary = str.split( ',' ), + l = ary.length, + i = 0, v; + for( ; i < l; ){ + v = ary[ i ]; + ret[ v ] = ++i; + ret[ i ] = v; + }; + return ret; +}; function XUI_Attr_createAttrDef( base, defs ){ var F = base ? X_Object_clone( base ) : {}, diff --git a/0.6.x/js/20_ui/06_AbstractUINode.js b/0.6.x/js/20_ui/06_AbstractUINode.js index 3180d29..5bfc6c9 100644 --- a/0.6.x/js/20_ui/06_AbstractUINode.js +++ b/0.6.x/js/20_ui/06_AbstractUINode.js @@ -1,6 +1,6 @@ var XUI_AbstractUINode = X_EventDispatcher[ 'inherits' ]( 'X.UI._AbstractUINode', - X_Class.ABSTRACT | X_Class.PRIVATE_DATA, + X_Class.ABSTRACT, { phase : 0, dirty : XUI_Dirty.CLEAN, @@ -890,10 +890,10 @@ X.UI.AbstractUINode = X_Class_create( X_Class.ABSTRACT, { parent : function(){ - return X_Class_getPrivate( this ).parent; + return X_Pair_get( this ).parent; }, root : function(){ - return X_Class_getPrivate( this ).root; + return X_Pair_get( this ).root; }, /* @@ -901,7 +901,7 @@ X.UI.AbstractUINode = X_Class_create( * サポートされていない場合は無視される.親のレイアウトによって変わる */ attr : function( nameOrObject, valueOrUnit ){ - var p = X_Class_getPrivate( this ), + var p = X_Pair_get( this ), layout, k, def, attrs, v; if( nameOrObject && X_Type_isObject( nameOrObject ) ){ // setter @@ -938,22 +938,22 @@ X.UI.AbstractUINode = X_Class_create( }, listen : function( type, arg1, arg2, arg3 ){ - X_Class_getPrivate( this )[ 'listen' ]( type, arg1, arg2, arg3 ); + X_Pair_get( this )[ 'listen' ]( type, arg1, arg2, arg3 ); return this; }, listenOnce : function( type, arg1, arg2, arg3 ){ - X_Class_getPrivate( this )[ 'listenOnce' ]( type, arg1, arg2, arg3 ); + X_Pair_get( this )[ 'listenOnce' ]( type, arg1, arg2, arg3 ); return this; }, listening : function( type, arg1, arg2, arg3 ){ - return X_Class_getPrivate( this )[ 'listening' ]( type, arg1, arg2, arg3 ); + return X_Pair_get( this )[ 'listening' ]( type, arg1, arg2, arg3 ); }, unlisten : function( type, arg1, arg2, arg3 ){ - X_Class_getPrivate( this )[ 'unlisten' ]( type, arg1, arg2, arg3 ); + X_Pair_get( this )[ 'unlisten' ]( type, arg1, arg2, arg3 ); return this; }, dispatch : function( e ){ - return X_Class_getPrivate( this )[ 'dispatch' ]( e ); + return X_Pair_get( this )[ 'dispatch' ]( e ); }, nextNode : function(){ @@ -963,7 +963,7 @@ X.UI.AbstractUINode = X_Class_create( }, nodeIndex : function( v ){ - var data = X_Class_getPrivate( this ); + var data = X_Pair_get( this ); if( typeof v === 'number' ){ // data.nodeIndex( v ); return this; @@ -975,27 +975,27 @@ X.UI.AbstractUINode = X_Class_create( }, getX : function(){ // dirty の場合、rootData.calculate - return X_Class_getPrivate( this ).boxX; + return X_Pair_get( this ).boxX; }, getY : function(){ // dirty の場合、rootData.calculate - return X_Class_getPrivate( this ).boxY; + return X_Pair_get( this ).boxY; }, getAbsoluteX : function(){ // dirty の場合、rootData.calculate - return X_Class_getPrivate( this ).absoluteX; + return X_Pair_get( this ).absoluteX; }, getAbsoluteY: function(){ // dirty の場合、rootData.calculate - return X_Class_getPrivate( this ).absoluteY; + return X_Pair_get( this ).absoluteY; }, getWidth : function(){ // dirty の場合、rootData.calculate - return X_Class_getPrivate( this ).boxWidth; + return X_Pair_get( this ).boxWidth; }, getHeight : function(){ // dirty の場合、rootData.calculate - return X_Class_getPrivate( this ).boxHeight; + return X_Pair_get( this ).boxHeight; } } ); diff --git a/0.6.x/js/20_ui/08_Box.js b/0.6.x/js/20_ui/08_Box.js index 0a3ad33..1d01e5e 100644 --- a/0.6.x/js/20_ui/08_Box.js +++ b/0.6.x/js/20_ui/08_Box.js @@ -54,7 +54,7 @@ var XUI_Layout_Canvas = X[ 'UI' ][ 'Layout' ][ 'Canvas' ] = XUI_createLayout( { var XUI_Box = XUI_AbstractUINode.inherits( 'X.UI._Box', - X_Class.PRIVATE_DATA, // 現状 super 指定がないとconstructor未定擬時に親のconstructor が使われない + X_Class.NONE, { supportAttrs : XUI_Attr_createAttrDef( XUI_AbstractUINode.prototype.supportAttrs, XUI_Layout_Canvas.overrideAttrsForSelf ), @@ -69,7 +69,7 @@ var XUI_Box = XUI_AbstractUINode.inherits( scrollingX : 0, // TODO 現在のスクロール位置 scrollingY : 0, // TODO - Constructor : function( layout, args ){ + Constructor : function( user, layout, args ){ var i = 0, l = args.length || 1, j = -1, @@ -77,10 +77,12 @@ var XUI_Box = XUI_AbstractUINode.inherits( //if( !args.length ) args = [ args ]; - if( !this.User[ 'instanceOf' ]( X.UI.Box ) ){ + if( !user[ 'instanceOf' ]( X.UI.Box ) ){ //throw new Error( 'Box を継承したインスタンスだけが _Box のオーナーになれます' ); }; + this.User = user; + this.xnode = X_Doc_create( 'div' ); // すでに定義されていればそちらを採用 @@ -90,7 +92,7 @@ var XUI_Box = XUI_AbstractUINode.inherits( for( ; i < l; ++i ){ arg = args[ i ]; if( arg[ 'instanceOf' ] && arg[ 'instanceOf' ]( X.UI.AbstractUINode ) ){ - _data = X_Class_getPrivate( arg ); + _data = X_Pair_get( arg ); if( !uinodes ) this.uinodes = uinodes = []; uinodes[ ++j ] = _data; if( _data.parent ){ @@ -227,7 +229,7 @@ var XUI_Box = XUI_AbstractUINode.inherits( //console.log( '### AddAt ' + this.phase ) for( l = _uinodes.length; i < l; ++i ){ - data = X_Class_getPrivate( _uinodes[ i ] ); + data = X_Pair_get( _uinodes[ i ] ); _p1 = p1 && data.phase < 1; _p2 = p2 && data.phase < 2; _p1 && data.initialize( this.root, this.rootData, this.User, this ); @@ -253,7 +255,7 @@ var XUI_Box = XUI_AbstractUINode.inherits( //console.log( '### AddAt ' + this.phase ) for( ; i; ){ - data = X_Class_getPrivate( _uinodes[ --i ] ); + data = X_Pair_get( _uinodes[ --i ] ); if( ( n = uinodes.indexOf( data ) ) !== -1 ){ uinodes.splice( n, 1 ); data._remove(); @@ -302,32 +304,31 @@ var XUI_Box = XUI_AbstractUINode.inherits( X.UI.Box = X.UI.AbstractUINode.inherits( 'Box', X_Class.NONE, - XUI_Box, { Constructor : function(){ - X_Class_newPrivate( this, XUI_Layout_Canvas, arguments ); + X_Pair_create( this, XUI_Box( this, XUI_Layout_Canvas, arguments ) ); }, add : function( node /* , node, node ... */ ){ - X_Class_getPrivate( this ).addAt( this.numNodes() + 1, Array.prototype.slice.call( arguments ) ); + X_Pair_get( this ).addAt( this.numNodes() + 1, Array.prototype.slice.call( arguments ) ); return this; }, addAt : function( index, node /* , node, node ... */ ){ if( index < 0 ) index = 0; - X_Class_getPrivate( this ).addAt( arguments[ 0 ], Array.prototype.slice.call( arguments, 1 ) ); + X_Pair_get( this ).addAt( arguments[ 0 ], Array.prototype.slice.call( arguments, 1 ) ); return this; }, remove : function( node /* , node, node ... */ ){ - X_Class_getPrivate( this )[ 'remove' ]( Array.prototype.slice.call( arguments ) ); + X_Pair_get( this )[ 'remove' ]( Array.prototype.slice.call( arguments ) ); return this; }, removeAt : function( from, length ){ - X_Class_getPrivate( this ).removeAt( from, length ); + X_Pair_get( this ).removeAt( from, length ); return this; }, getNodesByClass : function( klass ){ var ret = [], - uinodes = X_Class_getPrivate( this ).uinodes, + uinodes = X_Pair_get( this ).uinodes, i, l, node; if( !uinodes || uinodes.length === 0 ) return ret; for( i = 0, l = uinodes.length; i < l; ++i ){ @@ -340,16 +341,16 @@ X.UI.Box = X.UI.AbstractUINode.inherits( return this.getNodeAt( 0 ); }, getLastChild : function(){ - var uinodes = X_Class_getPrivate( this ).uinodes; + var uinodes = X_Pair_get( this ).uinodes; return uinodes && uinodes.length && uinodes[ uinodes.length - 1 ].User || null; }, getNodeAt : function( index ){ if( index < 0 ) return null; - var uinodes = X_Class_getPrivate( this ).uinodes; + var uinodes = X_Pair_get( this ).uinodes; return uinodes && uinodes[ index ].User || null; }, numNodes : function(){ - var uinodes = X_Class_getPrivate( this ).uinodes; + var uinodes = X_Pair_get( this ).uinodes; return uinodes && uinodes.length || 0; } } @@ -391,10 +392,13 @@ X.UI.Box.presets = function(){ }; if( privateKlass ){ + /* + * スーパークラスの属性定義リストをレイアウトの持つ属性定義で上書きした新しい属性定義リストを作る。 + */ supports = XUI_Attr_createAttrDef( privateKlass.prototype.supportAttrs, layout.overrideAttrsForSelf ); klass = this.inherits( privateKlass ); - privateKlass.prototype.supportAttrs = supports, + privateKlass.prototype.supportAttrs = supports; privateKlass.prototype.attrClass = XUI_Attr_preset( privateKlass.prototype.attrClass, supports, attrs ); } else { supports = XUI_Attr_createAttrDef( shadow.prototype.supportAttrs, layout.overrideAttrsForSelf ); diff --git a/0.6.x/js/20_ui/11_VBox.js b/0.6.x/js/20_ui/11_VBox.js index d9becca..d56665b 100644 --- a/0.6.x/js/20_ui/11_VBox.js +++ b/0.6.x/js/20_ui/11_VBox.js @@ -2,14 +2,17 @@ var XUI_Layout_Vertical = X[ 'UI' ][ 'Layout' ][ 'Vertical' ] = XUI_createLayout name : 'VerticalLayout', + /* + * + */ overrideAttrsForSelf : { selectable : false, - role : [ 0, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.INIT_ONLY | XUI_Attr_Type.LIST, 'none,chrome,container' ], - width : [ '100%', XUI_Dirty.LAYOUT, XUI_Attr_USER.LAYOUT, XUI_Attr_Type.LENGTH | XUI_Attr_Type.PERCENT | XUI_Attr_Type.AUTO ], + role : [ 0, XUI_Dirty.CLEAN, XUI_Attr_USER.UINODE, XUI_Attr_Type.INIT_ONLY | XUI_Attr_Type.LIST, 'none,chrome,container' ], + width : [ '100%', XUI_Dirty.LAYOUT, XUI_Attr_USER.LAYOUT, XUI_Attr_Type.LENGTH | XUI_Attr_Type.PERCENT | XUI_Attr_Type.AUTO ], height : [ XUI_Attr_AUTO, XUI_Dirty.LAYOUT, XUI_Attr_USER.LAYOUT, XUI_Attr_Type.LENGTH | XUI_Attr_Type.PERCENT | XUI_Attr_Type.AUTO ], childWidth : [ XUI_Attr_AUTO, XUI_Dirty.LAYOUT, XUI_Attr_USER.LAYOUT, XUI_Attr_Type.LENGTH | XUI_Attr_Type.PERCENT | XUI_Attr_Type.AUTO ], childHeight : [ XUI_Attr_AUTO, XUI_Dirty.LAYOUT, XUI_Attr_USER.LAYOUT, XUI_Attr_Type.LENGTH | XUI_Attr_Type.PERCENT | XUI_Attr_Type.AUTO ], - gapY : [ 0, XUI_Dirty.LAYOUT, XUI_Attr_USER.LAYOUT, XUI_Attr_Type.LENGTH ] + gapY : [ 0, XUI_Dirty.LAYOUT, XUI_Attr_USER.LAYOUT, XUI_Attr_Type.LENGTH ] }, overrideAttrsForChild : { @@ -61,8 +64,8 @@ var XUI_Layout_Vertical = X[ 'UI' ][ 'Layout' ][ 'Vertical' ] = XUI_createLayout } else { w = 0; }; - if( _w < w ) _w = w; - }; + if( _w < w ) _w = w; + }; }; _y -= gapY; } else { @@ -83,17 +86,36 @@ var XUI_Layout_Vertical = X[ 'UI' ][ 'Layout' ][ 'Vertical' ] = XUI_createLayout if( !isNeedsDetection ){ data.boxX += x; - data.boxY += y; + data.boxY += y; }; return !ret; } }); -X.UI.VBox = X.UI.Box.presets( - XUI_Layout_Vertical, +var XUI_VBox; + +X.UI.VBox = X.UI.Box.inherits( 'VBox', + X_Class.NONE, { - gapY : '0.2em', - childWidth : '100%' - } -); + Constructor : function(){ + var supports; + + if( !XUI_VBox ){ + supports = XUI_Attr_createAttrDef( XUI_Box.prototype.supportAttrs, XUI_Layout_Vertical.overrideAttrsForSelf ); + + XUI_VBox = XUI_Box.inherits( + { + layout : XUI_Layout_Vertical, + supportAttrs : supports, + attrClass : XUI_Attr_preset( XUI_Box.prototype.attrClass, supports, { + gapY : '0.2em', + childWidth : '100%' + } ) + } + ); + }; + X_Pair_create( this, XUI_VBox( this, XUI_Layout_Vertical, arguments ) ); + } + }); + diff --git a/0.6.x/js/20_ui/12_HBox.js b/0.6.x/js/20_ui/12_HBox.js index 1e74391..1efff7a 100644 --- a/0.6.x/js/20_ui/12_HBox.js +++ b/0.6.x/js/20_ui/12_HBox.js @@ -86,10 +86,37 @@ var XUI_Layout_Horizontal = X[ 'UI' ][ 'Layout' ][ 'Horizontal' ] = XUI_createLa } }); +/* X.UI.HBox = X.UI.Box.presets( XUI_Layout_Horizontal, 'HBox', { gapX : '0.2em' } -); +);*/ + +var XUI_HBox; + +X.UI.HBox = X.UI.Box.inherits( + 'HBox', + X_Class.NONE, + { + Constructor : function(){ + var supports; + + if( !XUI_HBox ){ + supports = XUI_Attr_createAttrDef( XUI_Box.prototype.supportAttrs, XUI_Layout_Horizontal.overrideAttrsForSelf ); + + XUI_HBox = XUI_Box.inherits( + { + layout : XUI_Layout_Horizontal, + supportAttrs : supports, + attrClass : XUI_Attr_preset( XUI_Box.prototype.attrClass, supports, { + gapX : '0.2em' + } ) + } + ); + }; + X_Pair_create( this, XUI_HBox( this, XUI_Layout_Horizontal, arguments ) ); + } + }); \ No newline at end of file diff --git a/0.6.x/js/20_ui/13_TileBox.js b/0.6.x/js/20_ui/13_TileBox.js index f0efbaa..06c2a6d 100644 --- a/0.6.x/js/20_ui/13_TileBox.js +++ b/0.6.x/js/20_ui/13_TileBox.js @@ -78,6 +78,7 @@ var XUI_Layout_Tile = X[ 'UI' ][ 'Layout' ][ 'Tile' ] = XUI_createLayout( { } }); +/* X.UI.TileBox = X.UI.Box.presets( 'TileBox', XUI_Layout_Tile, @@ -87,4 +88,31 @@ X.UI.TileBox = X.UI.Box.presets( hCenter : true, vCenter : true } -); +);*/ + +X.UI.TileBox = X.UI.Box.inherits( + 'TileBox', + X_Class.NONE, + { + Constructor : function(){ + var supports; + + if( !XUI_TileBox ){ + supports = XUI_Attr_createAttrDef( XUI_Box.prototype.supportAttrs, XUI_Layout_Tile.overrideAttrsForSelf ); + + XUI_TileBox = XUI_Box.inherits( + { + layout : XUI_Layout_Tile, + supportAttrs : supports, + attrClass : XUI_Attr_preset( XUI_Box.prototype.attrClass, supports, { + gapX : '0.2em', + gapY : '0.2em', + hCenter : true, + vCenter : true + }) + } + ); + }; + X_Pair_create( this, XUI_TileBox( this, XUI_Layout_Tile, arguments ) ); + } + }); \ No newline at end of file diff --git a/0.6.x/js/20_ui/14_ChromeBox.js b/0.6.x/js/20_ui/14_ChromeBox.js index 5935ebd..82b387b 100644 --- a/0.6.x/js/20_ui/14_ChromeBox.js +++ b/0.6.x/js/20_ui/14_ChromeBox.js @@ -1,14 +1,14 @@ var XUI_ChromeBox = XUI_Box.inherits( '_ChromeBox', - X_Class.PRIVATE_DATA, + X_Class.NONE, { chromeNodes : null, containerNode : null, - Constructor : function( layout, args ){ + Constructor : function( user, layout, args ){ var uinodes, i, l, node, after, index = 0; - this[ 'Super' ]( layout, args ); + this[ 'Super' ]( user, layout, args ); uinodes = this.uinodes; l = i = uinodes.length; @@ -19,7 +19,7 @@ var XUI_ChromeBox = XUI_Box.inherits( if( this.containerNode ){ //throw new Error( 'ContainerNode が複数設定されています!ContainerNode はクロームボックスにひとつ、生成時に設定できます ' + node ); }; - this.containerNode = node.User; + this.containerNode = node.User; this._containerNode = node; } else { if( !this.chromeNodes ) this.chromeNodes = []; @@ -47,55 +47,54 @@ var XUI_ChromeBox = XUI_Box.inherits( X.UI.ChromeBox = X.UI.Box.inherits( 'ChromeBox', X_Class.NONE, - XUI_ChromeBox, { Constructor : function(){ - X_Class_newPrivate( this, XUI_Layout_Canvas, arguments ); + X_Pair_create( this, XUI_ChromeBox( this, XUI_Layout_Canvas, arguments ) ); }, add : function( node /* , node, node ... */ ){ - X_Class_getPrivate( this ).containerNode.addAt( this.numNodes(), Array.prototype.slice.call( arguments ) ); + X_Pair_get( this ).containerNode.addAt( this.numNodes(), Array.prototype.slice.call( arguments ) ); return this; }, addAt : function( index, node /* , node, node ... */ ){ - X_Class_getPrivate( this ).containerNode.addAt( index, Array.prototype.slice.call( arguments, 1 ) ); + X_Pair_get( this ).containerNode.addAt( index, Array.prototype.slice.call( arguments, 1 ) ); return this; }, remove : function( node /* , node, node ... */ ){ - X_Class_getPrivate( this ).containerNode[ 'remove' ]( arguments ); + X_Pair_get( this ).containerNode[ 'remove' ]( arguments ); return this; }, removeAt : function( from, length ){ - X_Class_getPrivate( this ).containerNode.removeAt( from, length ); + X_Pair_get( this ).containerNode.removeAt( from, length ); return this; }, getNodesByClass : function( klass ){ - return X_Class_getPrivate( this ).containerNode.User.getNodesByClass( klass ); + return X_Pair_get( this ).containerNode.User.getNodesByClass( klass ); }, getFirstChild : function(){ - return X_Class_getPrivate( this ).containerNode.User.getFirstChild(); + return X_Pair_get( this ).containerNode.User.getFirstChild(); }, getLastChild : function(){ - return X_Class_getPrivate( this ).containerNode.User.getLastChild(); + return X_Pair_get( this ).containerNode.User.getLastChild(); }, getNodeByUID : function( uid ){ - return X_Class_getPrivate( this ).containerNode.User.getNodeByUID(); + return X_Pair_get( this ).containerNode.User.getNodeByUID(); }, getNodeAt : function( index ){ - return X_Class_getPrivate( this ).containerNode.User.getNodeAt( index ); + return X_Pair_get( this ).containerNode.User.getNodeAt( index ); }, numNodes : function(){ - return X_Class_getPrivate( this ).containerNode.User.numNodes(); + return X_Pair_get( this ).containerNode.User.numNodes(); }, getContainerNode : function(){ - return X_Class_getPrivate( this ).containerNode.User; + return X_Pair_get( this ).containerNode.User; }, getChromeNodeAt : function( index ){ if( index < 0 ) return null; - var nodes = X_Class_getPrivate( this ).chromeNodes; + var nodes = X_Pair_get( this ).chromeNodes; return nodes ? nodes[ index ].User || null : null; }, numChromeNodes : function(){ - var nodes = X_Class_getPrivate( this ).chromeNodes; + var nodes = X_Pair_get( this ).chromeNodes; return nodes ? nodes.length : 0; } } diff --git a/0.6.x/js/20_ui/15_ScrollBox.js b/0.6.x/js/20_ui/15_ScrollBox.js index be6884f..1fe9486 100644 --- a/0.6.x/js/20_ui/15_ScrollBox.js +++ b/0.6.x/js/20_ui/15_ScrollBox.js @@ -44,7 +44,7 @@ var X_UI_ScrollBox_SUPPORT_ATTRS = { var XUI_ScrollBox = XUI_ChromeBox.inherits( '_ScrollBox', - X_Class.PRIVATE_DATA, + X_Class.NONE, { directionLockThreshold : 10, scrollXEnabled : true, @@ -104,9 +104,9 @@ var XUI_ScrollBox = XUI_ChromeBox.inherits( _containerNode : null, xnodeSlider : null, - Constructor : function( layout, args ){ - this[ 'Super' ]( layout, args ); - this._containerNode = X_Class_getPrivate( this.containerNode ); + Constructor : function( user, layout, args ){ + this[ 'Super' ]( user, layout, args ); + this._containerNode = X_Pair_get( this.containerNode ); this.xnodeSlider = this._containerNode.xnode[ 'className' ]( 'ScrollSlider' ).listen( X_EVENT_ANIME_END, this, X_UI_ScrollBox_onAnimeEnd ); this.xnode[ 'className' ]( 'ScrollBox' ); }, @@ -503,26 +503,26 @@ function X_UI_ScrollBox_onAnimeEnd( e ){ }; if( !X_UI_ScrollBox_resetPosition( this, this.bounceTime ) ){ this.isInTransition = false; - this.dispatch( XUI_Event.SCROLL_END ); + this[ 'dispatch' ]( XUI_Event.SCROLL_END ); }; return X_Callback_NONE; }; +// TODO Box の継承に! X.UI.ScrollBox = X.UI.ChromeBox.inherits( 'ScrollBox', X_Class.NONE, - XUI_ScrollBox, { Constructor : function(){ var args = [ - XUI_Layout_Vertical, + XUI_Layout_Canvas, { - name : 'ScrollBox-Scroller', - role : 'container', - width : 'auto', - minWidth : '100%', - height : 'auto', - minHeight : '100%' + name : 'ScrollBox-Scroller', + role : 'container', + width : 'auto', + minWidth : '100%', + height : 'auto', + minHeight : '100%' } ], i = arguments.length, @@ -541,16 +541,19 @@ X.UI.ScrollBox = X.UI.ChromeBox.inherits( }; }; - X_Class_newPrivate( + X_Pair_create( this, - XUI_Layout_Canvas, - [ - { - width : '100%', - height : '100%' - }, - X.UI.VBox.apply( 0, args ) - ] + XUI_ScrollBox( + this, + XUI_Layout_Canvas, + [ + { + width : '100%', + height : '100%' + }, + X.UI.VBox.apply( 0, args ) + ] + ) ); attr && this.attr( attr ); diff --git a/0.6.x/js/20_ui/17_Text.js b/0.6.x/js/20_ui/17_Text.js index 86913ba..b589337 100644 --- a/0.6.x/js/20_ui/17_Text.js +++ b/0.6.x/js/20_ui/17_Text.js @@ -1,14 +1,15 @@ var XUI_Text = XUI_AbstractUINode.inherits( '_Text', - X_Class.PRIVATE_DATA, + X_Class.NONE, { content : null, - Constructor : function( content ){ - if( !( this.User[ 'instanceOf' ]( X.UI.Text ) ) ){ + Constructor : function( user, content ){ + if( !( user[ 'instanceOf' ]( X.UI.Text ) ) ){ alert( 'Text を継承したインスタンスだけが _Text のオーナーになれます' ); }; - this.xnode = X_Doc_create( 'div' ); + this.User = user; + this.xnode = X_Doc_create( 'div' ); if( X_Type_isString( content ) && content ){ this.content = content; @@ -22,14 +23,14 @@ var XUI_Text = XUI_AbstractUINode.inherits( X.UI.Text = X.UI.AbstractUINode.inherits( 'Text', X_Class.NONE, - XUI_Text, { Constructor : function( opt_content, opt_attrObj ){ - X_Class_newPrivate( this, opt_content ); + X_Pair_create( this, XUI_Text( this, opt_content ) ); + X_Type_isObject( opt_attrObj = opt_attrObj || opt_content ) && this[ 'attr' ]( opt_attrObj ); }, content : function( v ){ - var data = X_Class_getPrivate( this ); + var data = X_Pair_get( this ); if( data.content !== v ){ data.xnode && data.xnode[ 'text' ]( v ); data.rootData.reserveCalc(); diff --git a/0.6.x/js/20_ui/20_PageRoot.js b/0.6.x/js/20_ui/20_PageRoot.js index c67f5b0..fdcec3a 100644 --- a/0.6.x/js/20_ui/20_PageRoot.js +++ b/0.6.x/js/20_ui/20_PageRoot.js @@ -3,7 +3,7 @@ var X_UI_rootData = null, function X_UI_eventRellay( e ){ var font = X_ViewPort_baseFontSize, - x = e.pageX / font, // clientX iOS4- で通らない? + x = e.pageX / font, // clientX は iOS4- で通らない? y = e.pageY / font, type = XUI_Event.NameToID[ e.type ], i = 0, @@ -69,7 +69,7 @@ function X_UI_eventRellay( e ){ var XUI_PageRoot = XUI_Box.inherits( '_PageRoot', - X_Class.FINAL | X_Class.PRIVATE_DATA, + X_Class.FINAL, { layout : XUI_Layout_Canvas, @@ -82,8 +82,8 @@ var XUI_PageRoot = XUI_Box.inherits( eventCounter : null, cursorStyle : null, - Constructor : function( layout, args ){ - this[ 'Super' ]( layout, args ); + Constructor : function( user, layout, args ){ + this[ 'Super' ]( user, layout, args ); if( X_ViewPort_readyState === X_EVENT_XDOM_READY ){ X_Timer_once( 0, this, this.start ); @@ -179,6 +179,29 @@ function XUI_PageRoot_onViewUpdate( e ){ this[ 'dispatch' ]( XUI_Event.LAYOUT_COMPLETE ); }; +//var XUI_PageRoot; + +X.UI.PageRoot = X.UI.Box.inherits( + 'PageRoot', + X_Class.NONE, + { + Constructor : function(){ + var supports; + + //if( !XUI_PageRoot ){ + supports = XUI_Attr_createAttrDef( XUI_Box.prototype.supportAttrs, XUI_Layout_Canvas.overrideAttrsForSelf ); + + XUI_PageRoot.prototype.layout = XUI_Layout_Canvas; + XUI_PageRoot.prototype.supportAttrs = supports; + XUI_PageRoot.prototype.attrClass = XUI_Attr_preset( XUI_Box.prototype.attrClass, supports, { + width : '100%', + height : '100%' + } ); + //}; + X_Pair_create( this, XUI_PageRoot( this, XUI_Layout_Canvas, arguments ) ); + } + }); +/* X.UI.PageRoot = X.UI.Box.presets( 'PageRoot', XUI_PageRoot, @@ -186,5 +209,5 @@ X.UI.PageRoot = X.UI.Box.presets( width : '100%', height : '100%' } -); +);*/ -- 2.11.0