/* * Copyright (C) 2012-2018 NS Solutions Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * hifive * version 1.3.3 * gitCommitId : 39990125704305e5c214bf547ea366973efbbf28 * build at 2018/12/03 19:59:07.837 (+0900) * (scopedglobals,util,async,cls,event,resource,controller,dataModel,view,modelWithBinding,validation,ui,api.geo,api.sqldb,api.storage,scene) */ (function($){ // ========================================================================= // // Prelude // // ========================================================================= var savedH5 = undefined; //h5存在チェック if (window.h5) { if (window.h5.env && (window.h5.env.version === '1.3.3')) { // 既にロード済みのhifiveと同じバージョンをロードしようとした場合は何もしない return; } //coexistのために既存のh5を退避 savedH5 = window.h5; } // h5空間を新規に作成。クロージャでくるんでいるので // 以降の各モジュールが見るh5はここで定義された(新しい)h5になる var h5 = {}; // ============================= // Expose to window // ============================= window.h5 = h5; h5.coexist = function() { window.h5 = savedH5; return h5; }; h5.env = { version: '1.3.3' }; // ========================================================================= // // Extenal Library // // ========================================================================= // ========================================================================= // // Modules // // ========================================================================= /* h5scopedglobals */ // ========================================================================= // // Scoped Globals // // ========================================================================= // ============================= // Misc Variables // ============================= /** * { (エラーコード): (フォーマット文字列) } マップ * * @private */ var errorCodeToMessageMap = {}; /** * { (エラーコード): (フォーマッタ関数) } マップ * * @private */ var errorCodeToCustomFormatterMap = {}; /** * ネットワークに繋がらない時にjqXhr.statusに格納されるコード(IE)。通信をリトライするかどうかの判定に使用。 * * @private */ var ERROR_INTERNET_CANNOT_CONNECT = 12029; /** * undefinedかどうかの判定で、typeofで判定する * * @private */ var TYPE_OF_UNDEFINED = 'undefined'; /** * Node.ELEMENT_NODE。IE8-ではNodeがないので自前で定数を作っている * * @private */ var NODE_TYPE_ELEMENT = 1; /** * Node.DOCUMENT_NODE。IE8-ではNodeがないので自前で定数を作っている * * @private */ var NODE_TYPE_DOCUMENT = 9; //============================= // Errors //============================= // ============================= // Misc Functions // ============================= /** * フレームワークエラーを発生させます。 * * @private * @param code {Number} エラーコード * @param msgParam {Any[]} フォーマットパラメータ * @param detail {Any} 追加のデータ(内容はAPIごとに異なる) */ function throwFwError(code, msgParam, detail) { var msg = null; var msgSrc = errorCodeToMessageMap[code]; var customFormatter = errorCodeToCustomFormatterMap[code]; if (customFormatter) { msg = customFormatter(code, msgSrc, msgParam, detail); } //カスタムフォーマッタがnull/undefinedを返した場合も標準フォーマッタにかける if (!msg && msgSrc) { msg = h5.u.str.format.apply(null, [msgSrc].concat(msgParam)); } if (msg) { //最後に必ずエラーコードを付ける msg += '(code=' + code + ')'; } var e = msg ? new Error(msg) : new Error('FwError: code = ' + code); if (code) { e.code = code; } if (detail) { e.detail = detail; } throw e; } /* del begin */ // テストのためにexposeする window.com = { htmlhifive: { throwFwError: throwFwError } }; /* del end */ /** * エラーコードとエラーメッセージのマップを追加します。 * * @private * @param mapObj {Object} { (エラーコード): (フォーマット文字列) }という構造のオブジェクト */ function addFwErrorCodeMap(mapObj) { for ( var code in mapObj) { if (mapObj.hasOwnProperty(code)) { errorCodeToMessageMap[code] = mapObj[code]; } } } /** * エラーコードとカスタムメッセージフォーマッタのマップを追加します。 * * @private * @param errorCode エラーコード * @param formatter カスタムメッセージフォーマッタ */ function addFwErrorCustomFormatter(errorCode, formatter) { errorCodeToCustomFormatterMap[errorCode] = formatter; } /** * 非同期APIのReject時の理由オブジェクトを作成します。 * * @private * @param code {Number} エラーコード * @param msgParam {Any[]} フォーマットパラメータ * @param detail {Any} 追加のデータ(内容はAPIごとに異なる) * @returns {Object} 理由オブジェクト */ function createRejectReason(code, msgParam, detail) { var msg = null; var f = errorCodeToMessageMap[code]; if (f) { var args = [f].concat(msgParam); msg = h5.u.str.format.apply(null, args); } return { code: code, message: msg, detail: detail }; } /** * 引数を配列化します。既に配列だった場合はそれをそのまま返し、 配列以外だった場合は配列にして返します。 ただし、nullまたはundefinedの場合はそのまま返します。 * * @private * @param value 値 * @returns 配列化された値、ただし引数がnullまたはundefinedの場合はそのまま */ function wrapInArray(value) { if (value == null) { return value; } return isArray(value) ? value : [value]; } /** * 相対URLを絶対URLに変換します。 * * @private * @param {String} relativePath 相対URL * @returns {String} 絶対パス */ var toAbsoluteUrl = (function() { var a = null; var span = null; var isHrefPropAbsoluteFlag = null; function isHrefPropAbsolute() { if (isHrefPropAbsoluteFlag === null) { a.setAttribute('href', './'); isHrefPropAbsoluteFlag = a.href !== './'; } return isHrefPropAbsoluteFlag; } return function(relativePath) { if (!a) { // a.hrefを使わない場合でも、a.hrefが使えるかどうかの判定でa要素を使用するので、最初の呼び出し時に必ずa要素を作る a = document.createElement('a'); } if (isHrefPropAbsolute()) { a.setAttribute('href', relativePath); return a.href; } // a.hrefが絶対パスにならない場合はinnerHTMLを使う if (!span) { span = document.createElement('span'); } span.innerHTML = ''; return span.firstChild.href; }; })(); /** * 引数が文字列かどうかを判定します。 * * @private * @param {Any} target 値 * @returns {boolean} 文字列ならtrue、そうでないならfalse */ function isString(target) { return typeof target === 'string'; } /** * DeferredオブジェクトがReject状態かどうかを判定します。 jQuery1.7でDeferred.isRejected/isResolvedはDeprecatedとなり、 * 1.8で削除された(代わりにstate()メソッドが1.7から追加された)ので、 使用可能なAPIを用いて判定します。 * * @private * @param {Object} dfd Deferredオブジェクト * @returns {Boolean} Rejected状態かどうか */ function isRejected(dfd) { if (dfd.isRejected) { return dfd.isRejected(); } //jQuery 1.7でisRejectedはDeprecatedになり、1.8.0で削除された return dfd.state() === 'rejected'; } /** * DeferredオブジェクトがReject状態かどうかを判定します。 jQuery1.7でDeferred.isRejected/isResolvedはDeprecatedとなり、 * 1.8で削除された(代わりにstate()メソッドが1.7から追加された)ので、 使用可能なAPIを用いて判定します。 * * @private * @param {Object} dfd Deferredオブジェクト * @returns {Boolean} Resolved状態かどうか */ function isResolved(dfd) { if (dfd.isResolved) { return dfd.isResolved(); } return dfd.state() === 'resolved'; } /** * 引数が名前空間として有効な文字列かどうかを判定します。 ただし、全角文字が含まれる場合はfalseを返します。 * * @private * @param {Any} property 値 * @returns {boolean} 名前空間として有効な文字列であればtrue、そうでないならfalse */ function isValidNamespaceIdentifier(property) { if (!isString(property)) { return false; } // 全角文字は考慮しない return !!property.match(/^[A-Za-z_\$][\w|\$]*$/); } /** * 文字列をHTMLにパースします *
* jQuery.parseHTMLがある(jQuery1.8以降)場合はjQuery.parseHTMLと同じです *
** ない場合はjQuery1.8以降のparseHTMLと同様の動作を実装しています。 *
* * @private * @param {String} data HTML文字列 * @param {Document} [context=document] createElementを行うDocumentオブジェクト。省略した場合はdocumentを使用します * @param {Boolean} [keppScripts=false] script要素を生成するかどうか。デフォルトは生成しない(false)です */ var parseHTML = $.parseHTML ? $.parseHTML : function(data, context, keepScripts) { if (!data || !isString(data)) { return null; } if (typeof context === 'boolean') { // context指定が省略された場合(第2引数がboolean)なら第2引数をkeepScripts指定として扱う keepScripts = context; context = false; } context = context || document; // タグで囲って、$()でパースできるようにする data = '* deferred.pipe()がjQuery1.8から非推奨となったため1.8以上の場合then()を、1.7以下の場合はpipe()を実行します。 * * @private * @param {Promise} promise Promiseオブジェクト * @param {Function} doneFilter doneコールバック * @param {Function} failFilter failコールバック * @param {Function} progressFilter progressコールバック * @returns {Promise} Promiseオブジェクト */ function thenCompat(promise, doneFilter, failFilter, progressFilter) { //curCSS()はjQuery1.8.0で削除されたメソッド。これの有無で1.8以上かどうかの判定を代理している return promise[$.hasOwnProperty('curCSS') ? 'pipe' : 'then'](doneFilter, failFilter, progressFilter); } /** * 渡されたオブジェクトがwindowオブジェクトかどうか判定する * * @private * @param {Any} obj * @returns {Boolean} objがwindowオブジェクトかどうか */ function isWindowObject(obj) { // nodeがdocumentを持ち、documentから得られるwindowオブジェクトがnode自身ならnodeをwindowオブジェクトと判定する return obj && obj.document && obj.document.nodeType === NODE_TYPE_DOCUMENT && getWindowOfDocument(obj.document) === obj; } /** * ノードからドキュメントを取得。 *
* 引数がdocumentノードなら引数をそのまま、ノードならownerDocument、windowオブジェクトならそのdocumentを返します。nodeがいずれにも該当しない場合はnullを返します。 *
* * @private * @param {DOM} node * @returns {Document} documentオブジェクト */ function getDocumentOf(node) { if (typeof node.nodeType === TYPE_OF_UNDEFINED) { // ノードではない if (isWindowObject(node)) { // windowオブジェクトならwindow.documentを返す return node.document; } return null; } if (node.nodeType === NODE_TYPE_DOCUMENT) { // nodeがdocumentの場合 return node; } // nodeがdocument以外(documentツリー属するノード)の場合はそのownerDocumentを返す return node.ownerDocument; } /** * documentオブジェクトからwindowオブジェクトを取得 * * @private * @param {Document} doc * @returns {Window} windowオブジェクト */ function getWindowOfDocument(doc) { // IE8-だと、windowとwindow.document.parentWindowで、同じwindowを指すが、"==="で比較するとfalseになる (#339) // イベントハンドラをバインドするターゲットがwindowである時は、window.document.parentWindowではなく // windowにバインドして、イベントハンドラのthis(コントローライベントハンドラの第2引数)をwindowにするため、 // window.document === doc の場合はparentWindowではなくwindowを返すようにしている // IE8-ではdocument.parentWindow、それ以外はdoc.defaultViewでwindowオブジェクトを取得 return window.document === doc ? window : doc.defaultView || doc.parentWindow; } /** * ノードからwindowオブジェクトを取得 * * @private * @param {DOM} node * @returns {Window} windowオブジェクト */ function getWindowOf(node) { return getWindowOfDocument(getDocumentOf(node)); } /** * 引数が配列かどうか判定 ** Array.isArrayがあるブラウザの場合はisArray===Array.isArrayです *
* * @private * @param {Any} obj * @returns {Boolean} */ var isArray = Array.isArray || (function() { // プロパティアクセスを減らすため、toStringをキャッシュ var toStringObj = Object.prototype.toString; return function(obj) { return toStringObj.call(obj) === '[object Array]'; }; })(); /** * 引数が関数かどうか判定 * * @private * @param {Any} obj * @returns {Boolean} */ var isFunction = (function() { // Android3以下、iOS4以下は正規表現をtypeofで判定すると"function"を返す // それらのブラウザでは、toStringを使って判定する if (typeof new RegExp() === 'function') { var toStringObj = Object.prototype.toString; return function(obj) { return toStringObj.call(obj) === '[object Function]'; }; } // 正規表現のtypeofが"function"にならないブラウザなら、typeofがfunctionなら関数と判定する return function(obj) { return typeof obj === 'function'; }; })(); /** * 複数のプロミスを待機する機能と、待機中のプロミスを外す機能を提供するクラス * * @private * @class * @param promises * @param doneCallback * @param failCallback * @param cfhIfFail */ function WaitingPromiseManager(promises, doneCallback, failCallback, cfhIfFail) { // 高速化のため、長さ1または0の場合はforを使わずにチェックする var length = promises ? promises.length : 0; var isPromise = h5.async.isPromise; var that = this; var resolveArgs = null; this._doneCallbackExecuter = function() { that._resolved = true; if (doneCallback) { if (resolveArgs) { // resolveArgsを生成している(=複数プロミス)の場合は各doneハンドラの引数を配列にしたものを第1引数に渡す doneCallback.call(this, resolveArgs); } else { // 一つのpromiseにdoneハンドラを引っかけた場合はdoneハンドラの引数をそのままcallbackに渡す doneCallback.apply(this, arguments); } } }; this._failCallbackExecuter = function() { that._rejected = true; if (failCallback) { failCallback.apply(this, arguments); } else if (cfhIfFail && h5.settings.commonFailHandler) { // failCallbackが渡されていなくてcfhIfFailがtrueでcommonFailHandlerが設定されていればcFHを呼ぶ h5.settings.commonFailHandler.call(this, arguments); } }; // 高速化のため、長さ1またはプロミスを直接指定の場合はプロミス配列を作らない if (length === 1 || isPromise(promises)) { var promise = length === 1 ? promises[0] : promises; if (!isPromise(promise)) { // 長さ1で中身がプロミスでない場合はdoneCallback実行して終了 this._doneCallbackExecuter(); return; } // プロミス配列を作っていない場合のremoveをここで定義(プロトタイプのremoveを上書き) this.remove = function(p) { if (this._resolved || this._rejected) { return; } if (promise === p) { this._doneCallbackExecuter(); } }; // 長さ1で、それがプロミスなら、そのプロミスにdoneとfailを引っかける promise.done(this._doneCallbackExecuter); promise.fail(this._failCallbackExecuter); return; } // promisesの中のプロミスオブジェクトの数(プロミスでないものは無視) // 引数に渡されたpromisesのうち、プロミスオブジェクトと判定したものを列挙 var monitoringPromises = []; for (var i = 0, l = promises.length; i < l; i++) { var p = promises[i]; if (isPromise(p)) { monitoringPromises.push(p); } } var promisesLength = monitoringPromises.length; if (promisesLength === 0) { // プロミスが一つもなかった場合は即doneCallbackを実行 this._resolved = true; doneCallback && doneCallback(); return; } this._promises = monitoringPromises; resolveArgs = []; this._resolveArgs = []; this._resolveCount = 0; this._promisesLength = promisesLength; // いずれかのpromiseが成功するたびに全て終わったかチェックする関数 function createCheckFunction(_promise) { // いずれかのpromiseが成功するたびに全て終わったかチェックする関数 return function check(/* var_args */) { if (that._rejected) { // 既にいずれかがreject済みならなにもしない return; } var arg = h5.u.obj.argsToArray(arguments); // 引数無しならundefined、引数が一つならそのまま、引数が2つ以上なら配列を追加 // ($.when()と同じ) var index = $.inArray(_promise, promises); resolveArgs[index] = (arg.length < 2 ? arg[0] : arg); if (++that._resolveCount === that._promisesLength) { // 全てのpromiseが成功したので、doneCallbackを実行 that._doneCallbackExecuter(); } }; } for (var i = 0; i < promisesLength; i++) { var targetPromise = monitoringPromises[i]; targetPromise.done(createCheckFunction(targetPromise)).fail(this._failCallbackExecuter); } } WaitingPromiseManager.prototype = $.extend(WaitingPromiseManager.prototype, { remove: function(promise) { if (this._resolved || this._rejected) { return; } if (promsie === this._promises) { this._doneCallback && doneCallback(); this._resolved = true; return; } var index = $.inArray(promise, this._promises); if (index === -1) { return; } // 待機中のpromisesからpromiseを外す this._promises.splice(index, 1); // 取り除くpromiseについてのresolveArgsを減らす this._resolveArgs.splice(index, 1); if (isResolved(promise)) { // 既にresolve済みなら何もしない return; } // キャッシュしてある待機中プロミスの個数を1減らす this._promisesLength--; if (that._resolveCount === this._promisesLength) { // 現在resolve済みの個数と、1減らした後の待機中プロミス個数が同じならdoneハンドラ実行 this._doneCallbackExecuter(); } } }); /** * 複数のプロミスが完了するのを待機する ** whenとは仕様が異なり、新しくdeferredは作らない。 *
* * @private * @param {Promise[]} promises * @param {Function} doneCallback doneコールバック * @param {Function} failCallback failコールバック * @param {Boolean} cfhIfFail 渡されたpromiseのいずれかが失敗した時にcFHを呼ぶかどうか。 * cFHを呼ぶときのthisは失敗したpromiseオブジェクト、引数は失敗したpromiseのfailに渡される引数 * @returns {WaitingPromiseManager} */ function waitForPromises(promises, doneCallback, failCallback, cfhIfFail) { return new WaitingPromiseManager(promises, doneCallback, failCallback, cfhIfFail); } //TODO あるオブジェクト下に名前空間を作ってexposeするようなメソッドを作る var h5internal = { core: { controllerInternal: null } }; /* ------ h5.u ------ */ (function() { // ========================================================================= // // Constants // // ========================================================================= // ============================= // Production // ============================= /** * undefinedのタイプ */ var TYPE_OF_UNDEFINED = 'undefined'; /** * シリアライザのバージョン */ var CURRENT_SEREALIZER_VERSION = '2'; // エラーコード /** * ns()、getByPathで引数の名前空間名にstring以外が渡されたときに発生するエラー */ var ERR_CODE_NAMESPACE_INVALID = 11000; /** * expose()で既に存在する名前空間が指定されたときに発生するエラー */ var ERR_CODE_NAMESPACE_EXIST = 11001; /** * serialize()に関数オブジェクトが渡されたときに発生するエラー */ var ERR_CODE_SERIALIZE_FUNCTION = 11002; /** * 現行のバージョンと違うバージョンでserialize()された文字列をdeserialize()しようとしたときに発生するエラー */ var ERR_CODE_SERIALIZE_VERSION = 11003; /** * deserialize()で型情報の判定に失敗したときに発生するエラー */ var ERR_CODE_DESERIALIZE_TYPE = 11004; /** * serialize()に渡されたオブジェクト/配列が循環参照を持つときに発生するエラー */ var ERR_CODE_CIRCULAR_REFERENCE = 11005; /** * deserialize()で値が不正でデシリアライズできない時に発生するエラー */ var ERR_CODE_DESERIALIZE_VALUE = 11006; /** * loadScript()に渡されたパスが不正(文字列以外、空文字、空白文字)である時に発生するエラー */ var ERR_CODE_INVALID_SCRIPT_PATH = 11007; /** * loadScript()に渡されたオプションが不正(プレーンオブジェクト、null、undefined)である時に発生するエラー */ var ERR_CODE_INVALID_OPTION = 11008; /** * deserialize()で引数に文字列でないものを渡されたときのエラー */ var ERR_CODE_DESERIALIZE_ARGUMENT = 11009; /** * loadScript() 読み込みに失敗した場合に発生するエラー */ var ERR_CODE_SCRIPT_FILE_LOAD_FAILD = 11010; // ============================= // Development Only // ============================= /* del begin */ /** * 各エラーコードに対応するメッセージ */ var errMsgMap = {}; errMsgMap[ERR_CODE_NAMESPACE_INVALID] = '{0} 名前空間の指定が不正です。名前空間として有効な文字列を指定してください。'; errMsgMap[ERR_CODE_NAMESPACE_EXIST] = '名前空間"{0}"には、プロパティ"{1}"が既に存在します。'; errMsgMap[ERR_CODE_SERIALIZE_FUNCTION] = 'Function型のオブジェクトは変換できません。'; errMsgMap[ERR_CODE_SERIALIZE_VERSION] = 'シリアライザのバージョンが違います。シリアライズされたバージョン:{0} 現行のバージョン:{1}'; errMsgMap[ERR_CODE_DESERIALIZE_TYPE] = '型指定子が不正です。'; errMsgMap[ERR_CODE_CIRCULAR_REFERENCE] = '循環参照が含まれています。'; errMsgMap[ERR_CODE_DESERIALIZE_VALUE] = '不正な値が含まれるため、デシリアライズできませんでした。'; errMsgMap[ERR_CODE_INVALID_SCRIPT_PATH] = 'スクリプトのパスが不正です。空文字以外の文字列、またはその配列を指定して下さい。'; errMsgMap[ERR_CODE_INVALID_OPTION] = '{0} オプションの指定が不正です。プレーンオブジェクトで指定してください。'; errMsgMap[ERR_CODE_DESERIALIZE_ARGUMENT] = 'deserialize() 引数の値が不正です。引数には文字列を指定してください。'; errMsgMap[ERR_CODE_SCRIPT_FILE_LOAD_FAILD] = 'スクリプトファイルの読み込みに失敗しました。URL:{0}'; // メッセージの登録 addFwErrorCodeMap(errMsgMap); /* del end */ // ========================================================================= // // Cache // // ========================================================================= // ========================================================================= // // Privates // // ========================================================================= // ============================= // Variables // ============================= /** * loadScript()によって追加されたjsファイルの絶対パスを保持するオブジェクト * * @private */ var addedJS = {}; /** * HTMLのエスケープルール * * @private */ var htmlEscapeRules = { '&': '&', '"': '"', '<': '<', '>': '>', "'": ''' }; /** * SCRIPTにonloadがあるかどうか * * @private */ var existScriptOnload = document.createElement('script').onload !== undefined; /** * RegExp#toStringで改行文字がエスケープされるかどうか。 IEはtrue * * @private */ var regToStringEscapeNewLine = new RegExp('\r\n').toString().indexOf('\r\n') === -1; // ============================= // Functions // ============================= /** * 型情報の文字列をコードに変換します。 * * @private * @returns {String} 型を表すコード(1字) */ function typeToCode(typeStr) { switch (typeStr) { case 'string': return 's'; case 'number': return 'n'; case 'boolean': return 'b'; case 'String': return 'S'; case 'Number': return 'N'; case 'Boolean': return 'B'; case 'infinity': return 'i'; case '-infinity': return 'I'; case 'nan': return 'x'; case 'date': return 'd'; case 'regexp': return 'r'; case 'array': return 'a'; case 'object': return 'o'; case 'null': return 'l'; case TYPE_OF_UNDEFINED: return 'u'; case 'undefElem': return '_'; case 'objElem': return '@'; } } /** * 文字列中の\(エスケープ文字)とその他特殊文字をエスケープ ** \\, \b, \f, \n, \r, \t をエスケープする *
** http://json.org/json-ja.html に載っているうちの \/ と \" 以外。 *
** \/はJSON.stringifyでもエスケープされず、$.parseJSONでは\/も\\/も\/に復元されるので、エスケープしなくてもしてもどちらでもよい。 * \"はserialize文字列組立時にエスケープするのでここではエスケープしない。 *
* * @private * @param {String} str * @param {Boolean} nlEscaped 改行コードがすでにエスケープ済みかどうか。正規表現をtoString()した文字列をエスケープする場合に使用する。 * 正規表現をtoString()した場合に改行がエスケープされるブラウザとそうでないブラウザがあるため、改行がescape済みかどうかを引数で取り、 * trueが指定されていた場合は改行以外をエスケープする。 * @returns {String} エスケープ後の文字列 */ function escape(str, nlEscaped) { if (isString(str)) { var ret = str; if (nlEscaped) { // 改行コードがすでにエスケープ済みの文字列なら、一旦通常の改行コードに戻して、再度エスケープ // IEの場合、RegExp#toString()が改行コードをエスケープ済みの文字列を返すため。 ret = ret.replace(/\\n/g, '\n').replace(/\\r/g, '\r'); } // \b は、バックスペース。正規表現で\bを使うと単語境界を表すが、[\b]と書くとバックスペースとして扱える ret = ret.replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace( /[\b]/g, '\\b').replace(/\f/g, '\\f').replace(/\t/g, '\\t'); return ret; } if (str instanceof String) { return new String(escape(str.toString())); } return str; } /** * エスケープされた改行とタブと\(エスケープ文字)をアンエスケープ * * @private * @param {String} str * @param {String} version デシリアライズ対象の文字列がシリアライズされた時のバージョン。'1'ならunescapeしない。 * @returns {String} エスケープ後の文字列 */ function unescape(str, version) { if (version === '1') { return str; } if (isString(str)) { // \に変換する\\は一度'\-'にしてから、改行とタブを元に戻す。 // '\-'を元に戻す。 return str.replace(/\\\\/g, '\\-').replace(/\\b/g, '\b').replace(/\\f/g, '\f').replace( /\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\t/g, '\t').replace(/\\-/g, '\\'); } if (str instanceof String) { return new String(unescape(str.toString())); } return str; } /** * 文字列中のダブルコーテーションをエスケープする * * @private * @param {String} str * @returns {String} エスケープ後の文字列 */ function escapeDoubleQuotation(str) { // オブジェクトまたは配列の場合、シリアライズした結果に対して、ダブルコーテーションもエスケープする。#459 // ダブルコーテーションを\"でエスケープし、エスケープで使用する\は\\にエスケープする。 // これはオブジェクトまたは配列のデシリアライズ時に$.parseJSONを使用していて、その際にダブルコーテーション及びバックスラッシュがアンエスケープされるためである。 // 例えば {'"':'"'} のようなキー名または値にダブルコーテーションを含むようなオブジェクトの場合、 // parseJSONはダブルコーテーションをエスケープするため、'{"\\"":"\\""}' のような文字列にしないとparseJSONで復元できない。 // '{"\"":"\""}' をparseJSONするとエラーになってしまう。 return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); } /** * 指定されたスクリプトファイルをロードして、スクリプト文字列を取得します。(loadScriptメソッド用) ** dataType:scriptを指定した場合のデフォルトの挙動は、スクリプトファイルの読み込み完了後に$.globalEval()で評価を行うため、 * convertersを上書きしています。 * * @private * @param {String} url 読み込み対象のスクリプトパス * @param {Boolean} async 非同期でロードを行うか (true:非同期 / false:同期) * @param {Boolean} cache キャッシュされた通信結果が存在する場合、その通信結果を使用するか (true:使用する/false:使用しない) */ function getScriptString(url, async, cache) { var df = h5.async.deferred(); // 複数のパラメータを配列でまとめて指定できるため、コールバックの実行をresolveWith/rejectWith/notifyWithで行っている h5.ajax({ url: url, async: async, cache: cache, dataType: 'script', converters: { 'text script': function(text) { return text; } } }).done(function() { var args = argsToArray(arguments); args.push(this.url); df.notifyWith(df, args); df.resolveWith(df, args); }).fail(function() { df.rejectWith(df, argsToArray(arguments)); }); return df.promise(); } // ========================================================================= // // Body // // ========================================================================= /** * ドット区切りで名前空間オブジェクトを生成します。 * (h5.u.obj.ns('sample.namespace')と呼ぶと、window.sample.namespaceとオブジェクトを生成します。) * すでにオブジェクトが存在した場合は、それをそのまま使用します。 引数にString以外、または、識別子として不適切な文字列が渡された場合はエラーとします。 * * @param {String} namespace 名前空間 * @memberOf h5.u.obj * @returns {Object} 作成した名前空間オブジェクト */ function ns(namespace) { if (!isString(namespace)) { // 文字列でないならエラー throwFwError(ERR_CODE_NAMESPACE_INVALID, 'h5.u.obj.ns()'); } var nsArray = namespace.split('.'); var len = nsArray.length; for (var i = 0; i < len; i++) { if (!isValidNamespaceIdentifier(nsArray[i])) { // 名前空間として不正な文字列ならエラー throwFwError(ERR_CODE_NAMESPACE_INVALID, 'h5.u.obj.ns()'); } } var parentObj = window; for (var i = 0; i < len; i++) { var name = nsArray[i]; if (typeof parentObj[name] === TYPE_OF_UNDEFINED) { parentObj[name] = {}; } parentObj = parentObj[name]; } // ループが終了しているので、parentObjは一番末尾のオブジェクトを指している return parentObj; } /** * 指定された名前空間に、オブジェクトの各プロパティをそれぞれ対応するキー名で公開(グローバルからたどれる状態に)します。 *
*
* expose('sample.namespace', { * funcA: function() { * return 'test'; * }, * value1: 10 * }); ** * 実行結果: (window.は省略可)
* また、{0.name}のように記述すると第2引数のnameプロパティの値で置換を行います。"0."は引数の何番目かを指し、第2引数を0としてそれ以降の引数のプロパティの値を採ることもできます。 *
** "0."は省略して単に{name}のように記述することもできます。また、{0.birthday.year}のように入れ子になっているプロパティを辿ることもできます。 *
** "."の代わりに"[]"を使ってプロパティにアクセスすることもできます。以下、使用例です。 *
** *
* var myValue = 10;
* h5.u.str.format('{0} is {1}', 'myValue', myValue);
*
*
* 実行結果: myValue is 10
*
* * *
* h5.u.str.format('{name} is at {address}', {
* name: 'Taro',
* address: 'Yokohama'
* });
*
*
* 実行結果: Taro is at Yokohama
*
* * *
* h5.u.str.format('{0} is born on {1.birthday.year}.', 'Taro', {
* birthday: {
* year: 1990
* }
* });
*
*
* 実行結果: Taro is born on 1990.
*
* * *
* h5.u.str.format('{0.name} likes {0.hobby[0]}. {1.name} likes {1.hobby[0]}.', {
* name: 'Taro',
* hobby: ['Traveling', 'Shopping']
* }, {
* name: 'Hanako',
* hobby: ['Chess']
* });
*
*
* 実行結果: Taro likes Traveling. Hanako likes Chess.
*
* * *
* h5.u.str.format('{0.0},{0.1},{0.2},…(長さ{length})', [2, 3, 5, 7]);
* // 以下と同じ
* h5.u.str.format('{0[0]},{0[1]},{0[2]},…(長さ{0.length})', [2, 3, 5, 7]);
*
*
* 実行結果: 2,3,5,…(長さ4)
*
*
* @param {String} str 文字列
* @param {Any} var_args 可変長引数。ただし1つ目にオブジェクトまたは配列を指定した場合はその中身で置換
* @returns {String} フォーマット済み文字列
* @name format
* @function
* @memberOf h5.u.str
*/
function format(str, var_args) {
if (str == null) {
return '';
}
var args = argsToArray(arguments).slice(1);
return str.replace(/\{(.+?)\}/g, function(m, c) {
if (/^\d+$/.test(c)) {
// {0}のような数値のみの指定の場合は引数の値をそのまま返す
var rep = args[parseInt(c, 10)];
if (typeof rep === TYPE_OF_UNDEFINED) {
// undefinedなら"undefined"を返す
return TYPE_OF_UNDEFINED;
}
return rep;
}
// 数値じゃない場合はオブジェクトプロパティ指定扱い
// 数値.で始まっていなければ"0."が省略されていると見做す
var path = /^\d+[\.|\[]/.test(c) ? c : '0.' + c;
var rep = getByPath(path, args);
if (typeof rep === TYPE_OF_UNDEFINED) {
return TYPE_OF_UNDEFINED;
}
return rep;
});
}
/**
* 指定されたHTML文字列をエスケープします。
*
* @param {String} str HTML文字列
* @returns {String} エスケープ済HTML文字列
* @name escapeHtml
* @function
* @memberOf h5.u.str
*/
function escapeHtml(str) {
if ($.type(str) !== 'string') {
return str;
}
return str.replace(/[&"'<>]/g, function(c) {
return htmlEscapeRules[c];
});
}
/**
* オブジェクトを、型情報を付与した文字列に変換します。
* * このメソッドが判定可能な型は、以下のとおりです。 *
* このメソッドで文字列化したオブジェクトはdeseriarizeメソッドで元に戻すことができます。 *
** object型はプレーンオブジェクトとしてシリアライズします。 渡されたオブジェクトがプレーンオブジェクトで無い場合、そのprototypeやconstructorは無視します。 *
** array型は連想配列として保持されているプロパティもシリアライズします。 *
** 循環参照を含むarray型およびobject型はシリアライズできません。例外をスローします。 *
** 内部に同一インスタンスを持つarray型またはobject型は、別インスタンスとしてシリアライズします。以下のようなarray型オブジェクトaにおいて、a[0]とa[1]が同一インスタンスであるという情報は保存しません。 * *
* a = []; * a[0] = a[1] = []; ** * *
* function型のオブジェクトは変換できません。例外をスローします。 * array型にfunction型のオブジェクトが存在する場合は、undefinedとしてシリアライズします。object型または連想配列にfunction型のオブジェクトが存在する場合は、無視します。 *
* * @param {Object} value オブジェクト * @returns {String} 型情報を付与した文字列 * @name serialize * @function * @memberOf h5.u.obj */ function serialize(value) { if (isFunction(value)) { throwFwError(ERR_CODE_SERIALIZE_FUNCTION); } // 循環参照チェック用配列 var objStack = []; function existStack(obj) { for (var i = 0, len = objStack.length; i < len; i++) { if (obj === objStack[i]) { return true; } } return false; } function popStack(obj) { for (var i = 0, len = objStack.length; i < len; i++) { if (obj === objStack[i]) { objStack.splice(i, 1); } } } function func(val) { var ret = val; var type = $.type(val); // プリミティブラッパークラスを判別する if (typeof val === 'object') { if (val instanceof String) { type = 'String'; } else if (val instanceof Number) { type = 'Number'; } else if (val instanceof Boolean) { type = 'Boolean'; } } // オブジェクトや配列の場合、JSON.stringify()を使って書けるが、json2.jsのJSON.stringify()を使った場合に不具合があるため自分で実装した。 switch (type) { case 'String': // stringの場合と同じ処理を行うため、breakしない case 'string': // String、string、両方の場合について同じ処理を行う // typeToCodeはStringなら'S'、stringなら's'を返し、区別される ret = typeToCode(type) + escape(ret); break; case 'Boolean': // String/stringの場合と同様に、Boolean/booleanでも同じ処理を行うためbreakしていないが、 // Boolean型の場合はvalueOfで真偽値を取得する ret = ret.valueOf(); case 'boolean': // Booleanの場合は'B0','B1'。booleanの場合は'b0','b1'に変換する ret = typeToCode(type) + ((ret) ? 1 : 0); break; case 'Number': ret = ret.valueOf(); if (($.isNaN && $.isNaN(val)) || ($.isNumeric && !$.isNumeric(val))) { if (val.valueOf() === Infinity) { ret = typeToCode('infinity'); } else if (val.valueOf() === -Infinity) { ret = typeToCode('-infinity'); } else { ret = typeToCode('nan'); } } ret = typeToCode(type) + ret; break; case 'number': if (($.isNaN && $.isNaN(val)) || ($.isNumeric && !$.isNumeric(val))) { if (val === Infinity) { ret = typeToCode('infinity'); } else if (val === -Infinity) { ret = typeToCode('-infinity'); } else { ret = typeToCode('nan'); } } else { ret = typeToCode(type) + ret; } break; case 'regexp': ret = typeToCode(type) + escape(ret.toString(), regToStringEscapeNewLine); break; case 'date': ret = typeToCode(type) + (+ret); break; case 'array': if (existStack(val)) { throwFwError(ERR_CODE_REFERENCE_CYCLE); } objStack.push(val); var indexStack = []; ret = typeToCode(type) + '['; for (var i = 0, len = val.length; i < len; i++) { indexStack[i.toString()] = true; var elm; if (!val.hasOwnProperty(i)) { elm = typeToCode('undefElem'); } else if ($.type(val[i]) === 'function') { elm = typeToCode(TYPE_OF_UNDEFINED); } else { elm = escapeDoubleQuotation(func(val[i])); } ret += '"' + elm + '"'; if (i !== val.length - 1) { ret += ','; } } var hash = ''; for ( var key in val) { if (indexStack[key]) { continue; } if ($.type(val[key]) !== 'function') { hash += '"' + escapeDoubleQuotation(escape(key)) + '":"' + escapeDoubleQuotation(func(val[key])) + '",'; } } if (hash) { ret += ((val.length) ? ',' : '') + '"' + typeToCode('objElem') + '{' + escapeDoubleQuotation(hash); ret = ret.replace(/,$/, ''); ret += '}"'; } ret += ']'; popStack(val); break; case 'object': if (existStack(val)) { throwFwError(ERR_CODE_CIRCULAR_REFERENCE); } objStack.push(val); ret = typeToCode(type) + '{'; for ( var key in val) { if (val.hasOwnProperty(key)) { if ($.type(val[key]) === 'function') { continue; } ret += '"' + escapeDoubleQuotation(escape(key)) + '":"' + escapeDoubleQuotation(func(val[key])) + '",'; } } ret = ret.replace(/,$/, ''); ret += '}'; popStack(val); break; case 'null': case TYPE_OF_UNDEFINED: ret = typeToCode(type); break; } return ret; } return CURRENT_SEREALIZER_VERSION + '|' + func(value); } /** * 型情報が付与された文字列をオブジェクトを復元します。 * * @param {String} value 型情報が付与された文字列 * @returns {Any} 復元されたオブジェクト * @name deserialize * @function * @memberOf h5.u.obj */ function deserialize(value) { if (!isString(value)) { throwFwError(ERR_CODE_DESERIALIZE_ARGUMENT); } value.match(/^(.)\|(.*)/); var version = RegExp.$1; // version1の場合はエラーにせず、現在のバージョンでunescapeをしない方法で対応している。 if (version !== '1' && version !== CURRENT_SEREALIZER_VERSION) { throwFwError(ERR_CODE_SERIALIZE_VERSION, [version, CURRENT_SEREALIZER_VERSION]); } var ret = RegExp.$2; function func(val) { /** * 型情報のコードを文字列に変換します。 * * @private * @returns {String} 型を表す文字列 */ function codeToType(typeStr) { switch (typeStr) { case 's': return 'string'; case 'n': return 'number'; case 'b': return 'boolean'; case 'S': return 'String'; case 'N': return 'Number'; case 'B': return 'Boolean'; case 'i': return 'infinity'; case 'I': return '-infinity'; case 'x': return 'nan'; case 'd': return 'date'; case 'r': return 'regexp'; case 'a': return 'array'; case 'o': return 'object'; case 'l': return 'null'; case 'u': return TYPE_OF_UNDEFINED; case '_': return 'undefElem'; case '@': return 'objElem'; } } val.match(/^(.)(.*)/); var type = RegExp.$1; ret = (RegExp.$2) ? RegExp.$2 : ''; if (type !== undefined && type !== '') { switch (codeToType(type)) { case 'String': ret = new String(unescape(ret, version)); break; case 'string': break; case 'Boolean': if (ret === '0' || ret === '1') { ret = new Boolean(ret === '1'); } else { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } break; case 'boolean': if (ret === '0' || ret === '1') { ret = ret === '1'; } else { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } break; case 'nan': if (ret !== '') { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } ret = NaN; break; case 'infinity': if (ret !== '') { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } ret = Infinity; break; case '-infinity': if (ret !== '') { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } ret = -Infinity; break; case 'Number': if (codeToType(ret) === 'infinity') { ret = new Number(Infinity); } else if (codeToType(ret) === '-infinity') { ret = new Number(-Infinity); } else if (codeToType(ret) === 'nan') { ret = new Number(NaN); } else { ret = new Number(ret); if (isNaN(ret.valueOf())) { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } } break; case 'number': ret = new Number(ret).valueOf(); if (isNaN(ret)) { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } break; case 'array': var obj; try { obj = $.parseJSON(ret); } catch (e) { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } if (!isArray(obj)) { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } var ret = []; for (var i = 0, l = obj.length; i < l; i++) { switch (codeToType(obj[i].substring(0, 1))) { case 'undefElem': // i番目に値を何も入れない場合と、 // i番目に値を入れてからdeleteする場合とで // lengthが異なる場合がある。 // 最後の要素にundefが明示的に入れられている場合は後者のやりかたでデシリアライズする必要がある ret[i] = undefined; delete ret[i]; break; case 'objElem': var extendObj = func(typeToCode('object') + obj[i].substring(1)); for ( var key in extendObj) { ret[unescape(key)] = extendObj[key]; } break; default: ret[i] = func(obj[i]); } } break; case 'object': var obj; try { obj = $.parseJSON(ret); } catch (e) { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } if (!$.isPlainObject(obj)) { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } var ret = {}; for ( var key in obj) { // プロパティキーはエスケープしたものになっている // 元のプロパティキー(エスケープ前)のものに変更して値を持たせる var val = func(obj[key]); ret[unescape(key)] = val; } break; case 'date': ret = new Date(parseInt(ret, 10)); break; case 'regexp': try { var matchResult = ret.match(/^\/(.*)\/(.*)$/); var regStr = unescape(matchResult[1], version); var flg = matchResult[2]; ret = new RegExp(regStr, flg); } catch (e) { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } break; case 'null': if (ret !== '') { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } ret = null; break; case TYPE_OF_UNDEFINED: if (ret !== '') { throwFwError(ERR_CODE_DESERIALIZE_VALUE); } ret = undefined; break; default: throwFwError(ERR_CODE_DESERIALIZE_TYPE); } } return unescape(ret, version); } return func(ret); } /** * オブジェクトがjQueryオブジェクトかどうかを返します。 * * @param {Object} obj オブジェクト * @returns {Boolean} jQueryオブジェクトかどうか * @name isJQueryObject * @function * @memberOf h5.u.obj */ function isJQueryObject(obj) { if (!obj || !obj.jquery) { return false; } return (obj.jquery === $().jquery); } /** * argumentsを配列に変換します。 * * @param {Arguments} args Arguments * @returns {Any[]} argumentsを変換した配列 * @name argsToArray * @function * @memberOf h5.u.obj */ function argsToArray(args) { return Array.prototype.slice.call(args); } /** * 指定された名前空間に存在するオブジェクトを取得します。 ** 第1引数に名前空間文字列、第2引数にルートオブジェクトを指定します。第2引数を省略した場合はwindowオブジェクトをルートオブジェクトとして扱います。 *
** 名前空間文字列はプロパティ名を"."区切りで記述子、ルートオブジェクトからのパスを記述します。また"."区切りの代わりに"[]"を使ってプロパティアクセスを表すことも可能です。 *
* *
* var rootObj = {
* a: {
* b: {
* c: [{
* d: 'hoge'
* }]
* }
* }
* };
* h5.u.obj.getByPath('a.b.c[0].d', rootObj);
* // → hoge
*
* window.hoge = {
* obj: rootObj
* };
* h5.u.obj.getByPath('hoge.obj.a.b.c[0].d');
* // → hoge
*
*
* @param {String} namespace 名前空間
* @param {Object} [rootObj=window] 名前空間のルートとなるオブジェクト。デフォルトはwindowオブジェクト。
* @returns {Any} その名前空間に存在するオブジェクト
* @name getByPath
* @function
* @memberOf h5.u.obj
*/
function getByPath(namespace, rootObj) {
if (!isString(namespace)) {
throwFwError(ERR_CODE_NAMESPACE_INVALID, 'h5.u.obj.getByPath()');
}
// 'ary[0]'のような配列のindex参照の記法に対応するため、'.'記法に変換する
namespace = namespace.replace(/\[(\d+)\]/g, function(m, c, index) {
if (index) {
// 先頭以外の場合は'[]'を外して'.'を付けて返す
return '.' + c;
}
// 先頭の場合は'[]'を外すだけ
return c;
});
var names = namespace.split('.');
var idx = 0;
if (names[0] === 'window' && (!rootObj || rootObj === window)) {
// rootObjが未指定またはwindowオブジェクトの場合、namespaceの最初のwindow.は無視する
idx = 1;
}
var ret = rootObj || window;
for (var len = names.length; idx < len; idx++) {
ret = ret[names[idx]];
if (ret == null) { // nullまたはundefinedだったら辿らない
break;
}
}
return ret;
}
/**
* インターセプタを作成します。
*
* @param {Function} pre インターセプト先関数の実行前に呼ばれる関数です。
* @param {Function} post インターセプト先関数の実行後に呼ばれる関数です。pre(),post()には引数としてinvocation(インターセプト対象の関数についてのオブジェクト)と
* data(preからpostへ値を渡すための入れ物オブジェクト)が渡されます。
pre
に指定した関数内でinvocation.proceed()
を呼んでください。
* proceed()
を呼ぶと対象の関数(invocation.func
)を呼び出し時の引数(invocation.args
)で実行します。
* proceed
自体は引数を取りません。* var lapInterceptor = h5.u.createInterceptor(function(invocation, data) { * // 開始時間をdataオブジェクトに格納 * data.start = new Date(); * // invocationを実行 * return invocation.proceed(); * }, function(invocation, data) { * // 終了時間を取得 * var end = new Date(); * // ログ出力 * this.log.info('{0} "{1}": {2}ms', this.__name, invocation.funcName, (end - data.start)); * }); ** * @returns {Function} インターセプタ * @name createInterceptor * @function * @memberOf h5.u */ function createInterceptor(pre, post) { return function(invocation) { var data = {}; var ret = pre ? pre.call(this, invocation, data) : invocation.proceed(); invocation.result = ret; if (!post) { return ret; } if (ret && isFunction(ret.promise) && !isJQueryObject(ret)) { var that = this; registerCallbacksSilently(ret, 'always', function() { post.call(that, invocation, data); }); return ret; } post.call(this, invocation, data); return ret; }; } // ============================= // Expose to window // ============================= expose('h5.u', { loadScript: loadScript, createInterceptor: createInterceptor }); /** * @namespace * @name str * @memberOf h5.u */ expose('h5.u.str', { startsWith: startsWith, endsWith: endsWith, format: format, escapeHtml: escapeHtml }); /** * @namespace * @name obj * @memberOf h5.u */ expose('h5.u.obj', { expose: expose, ns: ns, serialize: serialize, deserialize: deserialize, isJQueryObject: isJQueryObject, argsToArray: argsToArray, getByPath: getByPath }); })(); /* ------ h5.mixin ------ */ (function() { // ========================================================================= // // Constants // // ========================================================================= // ============================= // Production // ============================= // ------------------------------- // エラーコード // ------------------------------- /** addEventListenerに不正な引数が渡された */ var ERR_CODE_INVALID_ARGS_ADDEVENTLISTENER = 16000; // ============================= // Development Only // ============================= /* del begin */ var errMsgMap = {}; errMsgMap[ERR_CODE_INVALID_ARGS_ADDEVENTLISTENER] = 'addEventListenerには、イベント名(文字列)、イベントリスナ(関数)を渡す必要があります。'; // メッセージの登録 addFwErrorCodeMap(errMsgMap); /* del end */ // ========================================================================= // // Cache // // ========================================================================= // ========================================================================= // // Privates // // ========================================================================= // ============================= // Variables // ============================= // ============================= // Functions // ============================= /** * 受け取ったオブジェクトをイベントオブジェクトにする * * @private * @param {Object} event 任意のオブジェクト * @param {Object} target event.targetになるオブジェクト * @returns {Object} イベントオブジェクト */ function setEventProperties(event, target) { // ターゲットの追加 if (!event.target) { event.target = target; } // タイムスタンプの追加 if (!event.timeStamp) { event.timeStamp = new Date().getTime(); } // isDefaultPreventedがないなら、isDefaultPrevented()とpreventDefault()を追加 if (!event.isDefaultPrevented) { var _isDefaultPrevented = false; event.isDefaultPrevented = function() { return _isDefaultPrevented; }; event.preventDefault = function() { _isDefaultPrevented = true; }; } // isImmediatePropagationStoppedがないなら、isImmediatePropagationStopped()とstopImmediatePropagation()を追加 if (!event.isImmediatePropagationStopped) { var _isImmediatePropagationStopped = false; event.isImmediatePropagationStopped = function() { return _isImmediatePropagationStopped; }; event.stopImmediatePropagation = function() { _isImmediatePropagationStopped = true; }; } } // ========================================================================= // // Body // // ========================================================================= /** * Mixinのコンストラクタ *
* このクラスは自分でnewすることはありません。 * h5.mixin.createMixin(moduleObject)を呼ぶと渡されたモジュールオブジェクトについてのMixinインスタンスを返します。 *
** 作製したインスタンスのmix()を呼ぶと、モジュールオブジェクトとmixの引数に渡されたオブジェクトをミックスインしたオブジェクトを返します。 *
* *
* // set,getメソッドを持つモジュールオブジェクトのmixinを作成する例
* var mixin = h5.mixin.createMixin({
* set: function(p, v) {
* this[p] = v;
* },
* get: function(p) {
* return this[p];
* }
* });
* var target = {
* hoge: 'abc'
* };
* mixin.mix(target);
* mixin.get('hoge'); // 'abc'が返る
*
*
* * h5.mixin以下にこのクラスを実装したインスタンスを配置しています。 *
* * @class Mixin */ /** * @private * @param moduleObject mixinする元となるモジュールオブジェクト */ function Mixin(moduleObject) { // moduleObjectのプロパティキャッシュを作成 var props = {}; for ( var p in moduleObject) { var v = moduleObject[p]; // hasOwnPropertyがtrueでなければコピーしない // 関数、null、文字列リテラル、数値リテラル、真偽値リテラルのいずれかの場合のみコピー if (moduleObject.hasOwnProperty(p) && (isFunction(v) || v === null || typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) { props[p] = v; } } // mix, hasInterfaceはMixinのprototypeに持たせていて、それぞれインスタンスが持つ_mix, _hasInterfaceを呼んでいる // _mix, _hasInterfaceはmoduleObjectのプロパティキャッシュを参照したいため、Mixinインスタンスごとに定義している this._mix = function(target) { for ( var p in props) { // targetがもともと持っていたプロパティがあっても上書き target[p] = props[p]; } }; this._hasInterface = function(object) { for ( var p in props) { // プライベートなものはチェックしない if (h5.u.str.startsWith(p, '_')) { continue; } // hasOwnPropertyがtrueかどうかは判定せず、プロトタイプチェーン上にあってもよい // undefinedでなければそのプロパティを持っていると判定する if (typeof object[p] === TYPE_OF_UNDEFINED) { return false; } } return true; }; } $.extend(Mixin.prototype, { /** * 引数に渡されたオブジェクトと、モジュールオブジェクトとのミックスインを作成します。 ** 関数、null、文字列リテラル、数値リテラル、真偽値リテラルのいずれかの値を持つプロパティについてのみミックスインを行います。 *
* * @memberOf Mixin * @prop {Object} target */ mix: function(target) { return this._mix(target); }, /** * 引数に渡されたオブジェクトが、モジュールオブジェクトを実装しているかどうか判定します。 ** 関数、null、文字列リテラル、数値リテラル、真偽値リテラルのいずれかの値を持つモジュールオブジェクトのプロパティを、 * 引数に渡されたobjectが全て持っているならtrue、そうでないならfalseを返します。 *
** ただしモジュールオブジェクトで定義されたプライベートメンバ("_"始まり)のプロパティについてはチェックしません。 *
* * @memberOf Mixin * @prop {Object} object * @returns {Boolean} */ hasInterface: function(object) { return this._hasInterface(object); } }); function createMixin(moduleObject) { return new Mixin(moduleObject); } //------------------------------------------- //EventDispatcher //------------------------------------------- /** * イベントディスパッチャ ** イベントリスナを管理するクラスです。このクラスはnewできません。 *
** このモジュールをミックスインしたオブジェクトを作成したい場合は、h5.mixin.eventDispatcherのmix()メソッドを使用してください。 *
** 以下のクラスがイベントディスパッチャのメソッドを持ちます。 *
* 第一引数にイベント名、第二引数にイベントリスナを渡し、指定したイベントに指定したイベントリスナが登録済みかどうかを返します。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {String} type イベント名 * @param {Function} listener イベントリスナ * @returns {Boolean} 第一引数のイベント名に第二引数のイベントリスナが登録されているかどうか */ hasEventListener: function(type, listener) { if (!this._eventListeners) { return false; } var l = this._eventListeners[type]; if (!l || !this._eventListeners.hasOwnProperty(type)) { return false; } for (var i = 0, count = l.length; i < count; i++) { if (l[i] === listener) { return true; } } return false; }, /** * イベントリスナを登録します。 ** 第一引数にイベント名、第二引数にイベントリスナを渡し、イベントリスナを登録します。指定したイベントが起こった時にイベントリスナが実行されます。 *
** イベントリスナは、関数またはEventListener * インタフェースを実装するオブジェクト(handleEventプロパティに関数を持つオブジェクト)で指定できます。 *
* 指定したイベントに、指定したイベントリスナが既に登録されていた場合は何もしません。 *
** 同一のイベントに対して複数回addEventListener()を呼び、複数のイベントリスナを登録した場合は、イベント発火時に登録した順番に実行されます。 *
** 第3引数以降が指定されていても無視されます。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {String} type イベント名 * @param {Function|Object} listener イベントリスナまたはhandleEventを持つイベントリスナオブジェクト */ addEventListener: function(type, listener) { // 引数チェック // typeは文字列で、第2引数まで指定されていることをチェックする // listenerが関数またはイベントリスナオブジェクトかどうかは、実行時に判定し、関数でもイベントリスナオブジェクトでもない場合は実行しない if (arguments.length < 2 || !isString(type)) { throwFwError(ERR_CODE_INVALID_ARGS_ADDEVENTLISTENER); } if (listener == null || this.hasEventListener(type, listener)) { // nullまたはundefinedが指定されている、または既に登録済みのイベントリスナなら何もしない return; } if (!this._eventListeners) { this._eventListeners = {}; } if (!(this._eventListeners.hasOwnProperty(type))) { this._eventListeners[type] = []; } this._eventListeners[type].push(listener); }, /** * イベントリスナを削除します。 ** 第一引数にイベント名、第二引数にイベントリスナを渡し、指定したイベントから指定したイベントリスナを削除します。 *
** 指定したイベント名に指定したイベントリスナが登録されていない場合は何もしません。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {String} type イベント名 * @param {Function} listener イベントリスナ */ removeEventListener: function(type, listener) { if (!this.hasEventListener(type, listener)) { return; } var l = this._eventListeners[type]; for (var i = 0, count = l.length; i < count; i++) { if (l[i] === listener) { l.splice(i, 1); return; } } }, /** * イベントをディスパッチします ** イベントオブジェクトを引数に取り、そのevent.typeに登録されているイベントリスナを実行します。 * イベントオブジェクトにpreventDefault()関数を追加してイベントリスナの引数に渡して呼び出します。 *
** 戻り値は『イベントリスナ内でpreventDefault()が呼ばれたかどうか』を返します。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {Object} event イベントオブジェクト * @returns {Boolean} イベントリスナ内でpreventDefault()が呼ばれたかどうか。 */ dispatchEvent: function(event) { if (!this._eventListeners) { return; } var l = this._eventListeners[event.type]; if (!l) { return; } // リスナをslice(0)して、dispatchEventを呼んだ瞬間にどのリスナが呼ばれるか確定させる // (あるイベントのイベントリスナの中でadd/removeEventListenerされても、そのイベントが実行するイベントリスナには影響ない) l = l.slice(0); setEventProperties(event, this); // リスナーを実行。stopImmediatePropagationが呼ばれていたらそこでループを終了する。 for (var i = 0, count = l.length; i < count && !event.isImmediatePropagationStopped(); i++) { if (isFunction(l[i])) { l[i].call(event.target, event); } else if (l[i].handleEvent) { // イベントリスナオブジェクトの場合はhandleEventを呼ぶ // handleEvent内のコンテキストはイベントリスナオブジェクトなので、callは使わずにそのまま呼び出す l[i].handleEvent(event); } } return event.isDefaultPrevented(); } }; // ============================= // Expose to window // ============================= /** * @namespace h5.mixin */ h5.u.obj.expose('h5.mixin', { /** * Mixinクラスを引数に渡されたモジュールオブジェクトから作成します。 ** 作成したMixinクラスはモジュールオブジェクトとのmixinを行うクラスになります。 Mixinクラスについてはこちらをご覧ください。 *
* * @since 1.1.10 * @memberOf h5.mixin * @param {Object} moduleObject モジュールオブジェクト。mixinの元となるオブジェクト * @returns {Mixin} モジュールオブジェクトをもとに生成したMixinオブジェクト */ createMixin: createMixin, /** * EventDispatcherのMixin ** EventDispatcherのMixinです。このクラスが持つmix()メソッドを呼ぶと、オブジェクトにEventDispatcherの機能が追加されます。 *
** Mixinクラスについてはこちらをご覧ください。 *
** EventDispatcherクラスについてはこちらをご覧ください。 *
* * @since 1.1.10 * @memberOf h5.mixin * @type {Mixin} * @name eventDispatcher */ eventDispatcher: createMixin(eventDispatcherModule) }); })(); /* ------ h5.log ------ */ (function() { // ========================================================================= // // Constants // // ========================================================================= // ============================= // Production // ============================= // エラーコード /** * ログターゲット(targets)の指定が不正なときのエラーコード */ var ERR_CODE_LOG_TARGET_TYPE = 10000; /* * out.categoryのが指定されていないときのエラーコード * ERR_CODE_OUT_CATEGORY_INVALIDに統合したためver.1.1.0で廃止 * var ERR_CODE_OUT_CATEGORY_IS_NONE = 10001; */ /* * カテゴリが複数回指定されたときのエラーコード * 出力定義にマッチするかどうかは、カテゴリ名に加えてレベルしても判定するようになったので1.1.15で廃止 #410 * var ERR_CODE_CATEGORY_NAMED_MULTIPLE_TIMES = 10002; */ /** * ログレベルの指定が不正なときのエラーコード */ var ERR_CODE_LEVEL_INVALID = 10003; /** * 存在しないログターゲットを指定されたときのエラーコード */ var ERR_CODE_LOG_TARGETS_IS_NONE = 10004; /** * カテゴリに文字列以外または空文字を指定したときのエラーコード */ var ERR_CODE_CATEGORY_INVALID = 10005; /** * ログターゲット(targets)が複数回指定されたときのエラーコード */ var ERR_CODE_LOG_TARGETS_NAMED_MULTIPLE_TIMES = 10007; /** * ログターゲット(targets)に文字列以外または空文字を指定されたときのエラーコード */ var ERR_CODE_LOG_TARGETS_INVALID = 10008; /** * ログターゲット(target)にオブジェクト以外を指定されたときのエラーコード */ var ERR_CODE_LOG_TARGET_INVALID = 10009; /** * out.categoryが指定されていないときのエラーコード */ var ERR_CODE_OUT_CATEGORY_INVALID = 10010; /** * スタックトレース出力時に1行目(メッセージ部)に表示するトレース件数 */ var PREVIEW_TRACE_COUNT = 3; // ============================= // Development Only // ============================= /* del begin */ /** * 各エラーコードに対応するメッセージ */ var errMsgMap = {}; errMsgMap[ERR_CODE_LOG_TARGET_TYPE] = 'ログターゲットのtypeには、オブジェクト、もしくは"console"のみ指定可能です。'; errMsgMap[ERR_CODE_LEVEL_INVALID] = 'level"{0}"の指定は不正です。Number、もしくはtrace, info, debug, warn, error, noneを指定してください。'; errMsgMap[ERR_CODE_LOG_TARGETS_NAMED_MULTIPLE_TIMES] = 'ログターゲット"{0}"が複数回指定されています。'; errMsgMap[ERR_CODE_LOG_TARGETS_IS_NONE] = '"{0}"という名前のログターゲットはありません。'; errMsgMap[ERR_CODE_CATEGORY_INVALID] = 'categoryは必須項目です。空文字で無い文字列を指定して下さい。'; errMsgMap[ERR_CODE_LOG_TARGETS_INVALID] = 'ログターゲット(targets)の指定は1文字以上の文字列、または配列で指定してください。'; errMsgMap[ERR_CODE_LOG_TARGET_INVALID] = 'ログターゲット(target)の指定はプレーンオブジェクトで指定してください。'; errMsgMap[ERR_CODE_OUT_CATEGORY_INVALID] = 'outの各要素についてcategoryは文字列で指定する必要があります。'; // メッセージの登録 addFwErrorCodeMap(errMsgMap); /* del end */ // ========================================================================= // // Cache // // ========================================================================= // ========================================================================= // // Privates // // ========================================================================= // ============================= // Variables // ============================= var logLevel = { /** * ログレベル: ERROR * * @memberOf Log.LEVEL * @const {Object} ERROR * @type Number */ ERROR: 50, /** * ログレベル: WARN * * @memberOf Log.LEVEL * @const {Object} WARN * @type Number */ WARN: 40, /** * ログレベル: INFO * * @memberOf Log.LEVEL * @const {Object} INFO * @type Number */ INFO: 30, /** * ログレベル: DEBUG * * @memberOf Log.LEVEL * @const {Object} DEBUG * @type Number */ DEBUG: 20, /** * ログレベル: TRACE * * @memberOf Log.LEVEL * @const {Object} TRACE * @type Number */ TRACE: 10, /** * ログレベル: ALL * * @memberOf Log.LEVEL * @const {Object} ALL * @type Number */ ALL: 0, /** * ログレベル: NONE * * @memberOf Log.LEVEL * @const {Object} NONE * @type Number */ NONE: -1 }; // コンパイル済ログ設定 var compiledLogSettings = null; // ============================= // Functions // ============================= /** * 指定されたレベルを文字列に変換します。 */ function levelToString(level) { if (level === logLevel.ERROR) { return 'ERROR'; } else if (level === logLevel.WARN) { return 'WARN'; } else if (level === logLevel.INFO) { return 'INFO'; } else if (level === logLevel.DEBUG) { return 'DEBUG'; } else if (level === logLevel.TRACE) { return 'TRACE'; } } /** * 指定された文字列をレベルに変換します。 */ function stringToLevel(str) { if (str.match(/^error$/i)) { return logLevel.ERROR; } else if (str.match(/^warn$/i)) { return logLevel.WARN; } else if (str.match(/^info$/i)) { return logLevel.INFO; } else if (str.match(/^debug$/i)) { return logLevel.DEBUG; } else if (str.match(/^trace$/i)) { return logLevel.TRACE; } else if (str.match(/^all$/i)) { return logLevel.ALL; } else if (str.match(/^none$/i)) { return logLevel.NONE; } else { return null; } } /** * トレース情報からトレース結果のオブジェクト取得します。 ** 以下のようなオブジェクトを返します *
* *
* {
* all: maxStackSize maxStackSizeまでのトレース結果を改行で結合した文字列
* preview: 最大でPREVIEW_TRACE_COUNTまでのトレース結果を" <- "で結合した文字列 "[func1_2 () <- func1_1 () <- func1 () ...]"
* }
*
*
* @param {String[]} traces トレース結果
* @param {Integer} maxStackSize 最大トレース数
*/
function getFormattedTraceMessage(traces, maxStackSize) {
var result = {};
var slicedTraces = traces.slice(0, maxStackSize);
var previewLength = Math.min(PREVIEW_TRACE_COUNT, maxStackSize);
var preview = slicedTraces.slice(0, previewLength).join(' <- ');
if (slicedTraces.length > previewLength) {
preview += ' ...';
}
result.preview = preview;
result.all = slicedTraces.join('\n');
return result;
}
/**
* 指定されたFunction型のオブジェクトから、名前を取得します。
*
* @param {Function} fn
*/
function getFunctionName(fn) {
var ret = '';
if (!fn.name) {
var regExp = /^\s*function\s*([\w\-\$]+)?\s*\(/i;
regExp.test(fn.toString());
ret = RegExp.$1;
} else {
ret = fn.name;
}
return ret;
}
/**
* 指定されたFunction型のオブジェクトから、引数の型の一覧を取得します。
*/
function parseArgs(args) {
var argArray = h5.u.obj.argsToArray(args);
var result = [];
for (var i = 0, len = argArray.length; i < len; i++) {
result.push($.type(argArray[i]));
}
return result.join(', ');
}
// =========================================================================
//
// Body
//
// =========================================================================
/**
* コンソールにログを出力するログターゲット
*
* @name ConsoleLogTarget
* @constructor
*/
function ConsoleLogTarget() {
// 空コンストラクタ
}
ConsoleLogTarget.prototype = {
/**
* コンソールログターゲットの初期化を行います。
*
* @memberOf ConsoleLogTarget
* @function
* @param {Object} param 初期化パラメータ
*/
init: function(param) {
// 今は特定のパラメータはない
},
/**
* ログをコンソールに出力します。
*
* @memberOf ConsoleLogTarget
* @function
* @param {Object} logObj ログ情報を保持するオブジェクト
*/
log: function(logObj) {
if (!window.console) {
return;
}
var args = logObj.args;
if (!isString(args[0])) {
this._logObj(logObj);
} else {
this._logMsg(logObj);
}
},
/**
* 指定された文字列をコンソールに出力します。
*
* @memberOf ConsoleLogTarget
* @private
* @function
* @param {Object} logObj ログ情報を保持するオブジェクト
*/
_logMsg: function(logObj) {
var args = logObj.args;
var msg = null;
if (args.length === 1) {
msg = args[0];
} else {
msg = h5.u.str.format.apply(h5.u.str, args);
}
var logMsg = this._getLogPrefix(logObj) + msg;
if (logObj.logger.enableStackTrace) {
logMsg += ' [' + logObj.stackTrace.preview + ']';
}
if (logObj.logger.enableStackTrace && console.groupCollapsed) {
console.groupCollapsed(logMsg);
} else {
this._consoleOut(logObj.level, logMsg);
}
if (logObj.logger.enableStackTrace) {
// if (console.trace) {
// console.trace();
// } else {
this._consoleOut(logObj.level, logObj.stackTrace.all);
// }
}
if (logObj.logger.enableStackTrace && console.groupEnd) {
console.groupEnd();
}
},
_consoleOut: function(level, str) {
var logPrinted = false;
// 専用メソッドがあればそれを使用して出力
if ((level == logLevel.ERROR) && console.error) {
console.error(str);
logPrinted = true;
} else if ((level == logLevel.WARN) && console.warn) {
console.warn(str);
logPrinted = true;
} else if ((level == logLevel.INFO) && console.info) {
console.info(str);
logPrinted = true;
} else if ((level == logLevel.DEBUG) && console.debug) {
console.debug(str);
logPrinted = true;
}
if (!logPrinted && console.log) {
// this.trace()の場合、または固有メソッドがない場合はlogメソッドで出力
console.log(str);
}
},
/**
* 出力するログのプレフィックスを作成します。
*
* @memberOf ConsoleLogTarget
* @private
* @function
* @param {Object} logObj ログ情報を保持するオブジェクト
* @return ログのプレフィックス
*/
_getLogPrefix: function(logObj) {
return '[' + logObj.levelString + ']' + logObj.date.getHours() + ':'
+ logObj.date.getMinutes() + ':' + logObj.date.getSeconds() + ','
+ logObj.date.getMilliseconds() + ': ';
},
/**
* 指定されたオブジェクトをコンソールに出力します。
*
* @memberOf ConsoleLogTarget
* @private
* @function
* @param {Object} logObj ログ情報を保持するオブジェクト
*/
_logObj: function(logObj) {
// 専用メソッドがあればそれを使用して出力
var args = logObj.args;
var prefix = this._getLogPrefix(logObj);
args.unshift(prefix);
if ((logObj.level == logLevel.ERROR) && console.error) {
this._output(console.error, args);
} else if ((logObj.level == logLevel.WARN) && console.warn) {
this._output(console.warn, args);
} else if ((logObj.level == logLevel.INFO) && console.info) {
this._output(console.info, args);
} else if ((logObj.level == logLevel.DEBUG) && console.debug) {
this._output(console.debug, args);
} else {
this._output(console.log, args);
}
},
_output: function(func, args) {
try {
// IEでは、console.log/error/info/warnにapplyがない。
// IE11ではapplyを参照しただけでエラーが発生するので、
// try-catchの中でfunc.applyがあるかどうか確認する
if (func.apply) {
// IE以外では、applyを使って呼び出さないと『TypeError:Illegal invocation』が発生する
func.apply(console, args);
return;
}
} catch (e) {
// 何もしない
}
func(args);
}
};
/**
* h5.settings.logにあるログ設定を適用します。
*
* @function
* @name configure
* @memberOf h5.log
*/
var configure = function() {
// defaultOutのデフォルト
var defaultOut = {
level: 'NONE',
targets: null
};
/* del begin */
// h5.dev.jsではデフォルトのdefaultOutをログ出力するようにしておく。
defaultOut = {
level: 'debug',
targets: 'console'
};
/* del end */
// 設定オブジェクト
var settings = $.extend(true, {}, h5.settings.log);
// デフォルトアウトの設定
var dOut = settings.defaultOut;
if (!dOut) {
dOut = defaultOut;
settings.defaultOut = dOut;
}
function compileLogTarget(targets) {
if (!$.isPlainObject(targets)) {
throwFwError(ERR_CODE_LOG_TARGET_INVALID);
}
for ( var prop in targets) {
var obj = targets[prop];
var type = $.type(obj.type);
// 今は"remote"でもエラーとなる
if (type !== 'object' && obj.type !== 'console') {
throwFwError(ERR_CODE_LOG_TARGET_TYPE);
}
var compiledTarget = null;
if (obj.type === 'console') {
compiledTarget = new ConsoleLogTarget();
} else {
// typeがオブジェクトの場合
var clone = $.extend(true, {}, obj.type);
compiledTarget = clone;
}
if (compiledTarget.init) {
compiledTarget.init(obj);
}
obj.compiledTarget = compiledTarget;
}
targets.console = {
type: 'console',
compiledTarget: new ConsoleLogTarget()
};
}
var categoryCache = [];
function compileOutput(_logTarget, out, isDefault) {
if (!isDefault) {
// デフォルトアウトでない場合はcategoryの設定を行う
var category = out.category;
if (!isString(category) || $.trim(category).length === 0) {
throwFwError(ERR_CODE_OUT_CATEGORY_INVALID);
}
category = $.trim(category);
out.compiledCategory = getRegex(category);
categoryCache.push(category);
}
// レベルのコンパイル(数値化)
var compiledLevel;
if (out.level == null) {
compiledLevel = stringToLevel(isDefault ? defaultOut.level : dOut.level);
} else {
compiledLevel = isString(out.level) ? stringToLevel($.trim(out.level)) : out.level;
}
if (typeof compiledLevel !== 'number') {
throwFwError(ERR_CODE_LEVEL_INVALID, out.level);
}
out.compiledLevel = compiledLevel;
// ターゲットのコンパイル
var compiledTargets = [];
var targets = out.targets;
if (targets != null) {
var targetNames = [];
// targetsの指定は文字列または配列またはnull,undefinedのみ
if (!(targets == null || isArray(targets) || (isString(targets) && $.trim(targets).length))) {
throwFwError(ERR_CODE_LOG_TARGETS_INVALID);
}
targets = wrapInArray(targets);
for (var i = 0, len = targets.length; i < len; i++) {
if (!(targets[i] == null || (isString(targets[i]) && $.trim(targets[i]).length))) {
throwFwError(ERR_CODE_LOG_TARGETS_INVALID);
}
var targetName = targets[i];
if (!targetName) {
continue;
}
if ($.inArray(targetName, targetNames) !== -1) {
throwFwError(ERR_CODE_LOG_TARGETS_NAMED_MULTIPLE_TIMES, targetName);
}
var l = _logTarget[targetName];
if (!l) {
throwFwError(ERR_CODE_LOG_TARGETS_IS_NONE, targetName);
}
targetNames.push(targetName);
compiledTargets.push(l.compiledTarget);
}
}
out.compiledTargets = compiledTargets;
}
// ログターゲットをコンパイル
var logTarget = settings.target;
if (!logTarget) {
logTarget = {};
settings.target = logTarget;
}
compileLogTarget(logTarget);
// 出力定義をコンパイル
compileOutput(logTarget, dOut, true);
var outs = settings.out;
if (outs) {
outs = wrapInArray(outs);
for (var i = 0, len = outs.length; i < len; i++) {
compileOutput(logTarget, outs[i]);
}
}
// ここまでの処理でエラーが起きなかったら設定を適用する
compiledLogSettings = settings;
};
/**
* ログを生成するクラス
*
* @class
* @name Log
*/
function Log(category) {
// categoryの指定が文字列以外、または空文字、空白文字ならエラー。
if (!isString(category) || $.trim(category).length === 0) {
throwFwError(ERR_CODE_CATEGORY_INVALID);
}
/**
* ログカテゴリ
*
* @memberOf Log
* @type String
* @name category
*/
this.category = $.trim(category);
}
Log.prototype = {
/**
* ログ出力時、スタックトレース(関数呼び出し関係)を表示するか設定します。* 引数がObject型の場合はオブジェクト構造を、String型の場合は引数の書式に合わせてログを出力します。 *
* 書式については、h5.u.str.format関数のドキュメントを参照下さい。 * * @see h5.u.str.format * @memberOf Log * @function * @param {Any} var_args コンソールに出力する内容 */ error: function(var_args) { this._log(logLevel.ERROR, arguments, this.error); }, /** * LEVEL.WARN レベルのログを出力します。 *
* 引数がObject型の場合はオブジェクト構造を、String型の場合は引数の書式に合わせてログを出力します。 *
* 書式については、h5.u.str.format関数のドキュメントを参照下さい。 * * @see h5.u.str.format * @memberOf Log * @function * @param {Any} var_args コンソールに出力する内容 */ warn: function(var_args) { this._log(logLevel.WARN, arguments, this.warn); }, /** * LEVEL.INFO レベルのログを出力します。 *
* 引数がObject型の場合はオブジェクト構造を、String型の場合は引数の書式に合わせてログを出力します。 *
* 書式については、h5.u.str.format関数のドキュメントを参照下さい。 * * @see h5.u.str.format * @memberOf Log * @function * @param {Any} var_args コンソールに出力する内容 */ info: function(var_args) { this._log(logLevel.INFO, arguments, this.info); }, /** * LEVEL.DEBUG レベルのログを出力します。 *
* 引数がObject型の場合はオブジェクト構造を、String型の場合は引数の書式に合わせてログを出力します。 *
* 書式については、h5.u.str.format関数のドキュメントを参照下さい。 * * @see h5.u.str.format * @function * @memberOf Log * @param {Any} var_args コンソールに出力する内容 */ debug: function(var_args) { this._log(logLevel.DEBUG, arguments, this.debug); }, /** * LEVEL.TRACE レベルのログを出力します。 *
* 引数がObject型の場合はオブジェクト構造を、String型の場合は引数の書式に合わせてログを出力します。 *
* 書式については、h5.u.str.format関数のドキュメントを参照下さい。 * * @see h5.u.str.format * @memberOf Log * @function * @param {Any} var_args コンソールに出力する内容 */ trace: function(var_args) { this._log(logLevel.TRACE, arguments, this.trace); }, /** * スタックトレース(関数呼び出し関係)を取得します。 * * @private * @memberOf Log * @function * @param fn {Function} トレース対象の関数 * @returns {Object} 以下のようなオブジェクトを返します * *
* {
* all: maxStackSize maxStackSizeまでのトレース結果を改行で結合した文字列
* preview: 最大でPREVIEW_TRACE_COUNTまでのトレース結果を" <- "で結合した文字列 "[func1_2 () <- func1_1 () <- func1 () ...]"
* }
*
*/
_traceFunctionName: function(fn) {
var traces = [];
// エラーオブジェクトを生成してスタックトレースを取得
var e = new Error();
var errMsg = e.stack || e.stacktrace;
if (errMsg) {
// stackまたはstacktraceがある場合(IE,Safari以外)
// stackにはFW内部の関数も含まれるので、それを取り除く
// new Error()を呼んだ場合に関数呼び出し3つ分省略すればいいが、minifyするとnew演算子が省略される
// Chrome,FireFoxではnew演算子を省略するコンストラクト呼び出しがstackに入り、newを使った場合と結果が異なる
// Operaではnew演算子を省略しても結果は変わらない
// min版、dev版の互換とブラウザ互換をとるために、取り除く関数呼び出しはここの関数名を基点に数えて取り除く
var CURRENT_FUNCTION_REGEXP = /_traceFunctionName/;
// トレースされたログのうち、ここの関数から3メソッド分先までの関数呼び出しはログに出力しない。
// (_traceFunction, _log, debug|info|warn|error|trace の3つ。この関数+2の箇所でsliceする)
var DROP_TRACE_COUNT = 3;
traces = errMsg.replace(/\r\n/, '\n').replace(
/at\b|@|Error\b|\t|\[arguments not available\]/ig, '').replace(
/(http|https|file):.+[0-9]/g, '').replace(/ +/g, ' ').split('\n');
var ret = null;
var currentFunctionIndex = null;
traces = $.map(traces, function(value, index) {
if (value.length === 0) {
// 不要なデータはnullを返して削除(Chromeは配列の先頭, FireFoxは配列の末尾に存在する)
// ただしslice箇所が決定する前は削除しない(nullを返さないようにする)
return currentFunctionIndex == null ? '' : null;
} else if ($.trim(value) === '') {
ret = '{anonymous}'; // ログとして出力されたが関数名が無い
} else {
ret = $.trim(value);
}
if (currentFunctionIndex === null && CURRENT_FUNCTION_REGEXP.test(value)) {
currentFunctionIndex = index;
}
return ret;
}).slice(currentFunctionIndex + DROP_TRACE_COUNT);
} else {
// IE, Safari
// 呼び出された関数を辿って行ったときに"use strict"宣言を含む関数がある場合、
// IE11だとcallerプロパティへアクセスすると以下のようにエラーが発生する
// 『strict モードでは、関数または arguments オブジェクトの 'caller' プロパティを使用できません』
// (例えばjQuery1.9.0は"use strict"宣言がされており、jQuery1.9.0内の関数を経由して呼ばれた関数は全てstrictモード扱いとなり、
// callerプロパティにアクセスできない)
// そのため、try-catchで囲んで、取得できなかった場合は{unable to trace}を出力する
// fnは、(debug|info|warn|error|trace)の何れかなので、その呼び出し元から辿る
var caller = fn.caller;
for (var i = 0, l = this.maxStackSize; i < l; i++) {
var funcName = getFunctionName(caller);
var argStr = parseArgs(caller.arguments);
var nextCaller;
try {
nextCaller = caller.caller;
} catch (e) {
// エラーが発生してトレースできなくなったら終了
traces.push('{unable to trace}');
break;
}
if (funcName) {
// 関数名が取得できているときは関数名を表示
traces.push('{' + funcName + '}(' + argStr + ')');
} else if (nextCaller) {
// 関数名は取得できていなくても次の関数ができているなら{anonymous}として表示
traces.push('{anonymous}(' + argStr + ')');
} else {
// 次の関数が無い場合はルートからの呼び出し
traces.push('{root}(' + argStr + ')');
}
if (!nextCaller) {
// これ以上トレースできないので終了
break;
}
caller = nextCaller;
}
}
return getFormattedTraceMessage(traces, this.maxStackSize);
},
/**
* ログ情報を保持するオブジェクトに以下の情報を付与し、コンソールまたはリモートサーバにログを出力します。
* * URL全体がこの値を超えた場合、開発字はエラー、運用時は警告ログを出力。 IEで2084の場合があり、これ以下で、ある程度のバッファを取った。 *
*/ var URL_MAX_LENGTH = 1800; /** * シーンのデフォルトのヒストリーモード */ var DEFAULT_HISTORY_MODE = 'history'; // ============================= // Development Only // ============================= /* del begin */ /* del end */ // ========================================================================= // // Cache // // ========================================================================= // ========================================================================= // // Privates // // ========================================================================= // ============================= // Variables // ============================= // ============================= // Functions // ============================= /** * すべてのアスペクト設定をコンパイルします。 * * @param {Object|Object[]} aspects アスペクト設定 */ function compileAspects(aspects) { var compile = function(aspect) { if (aspect.target) { aspect.compiledTarget = getRegex(aspect.target); } if (aspect.pointCut) { aspect.compiledPointCut = getRegex(aspect.pointCut); } return aspect; }; h5.settings.aspects = $.map(wrapInArray(aspects), function(n) { return compile(n); }); } /** * h5.ajax()でリトライする時のデフォルトのフィルタ* falseを返した場合はリトライしない。リトライする場合はリトライするajaxSettingsオブジェクト($.ajax()に渡すオブジェクト)を返す。 * type===GETかつステータスコードが0(タイムアウト)または12029(IEでコネクションが繋がらない)場合にリトライする。 *
** 引数は$.ajaxのfailコールバックに渡されるものが入る。 thisはajaxを呼んだ時の設定パラメータを含むajaxSettingsオブジェクト *
* * @param {jqXHR} jqXHR jqXHRオブジェクト * @param {String} textStatus * @param {String} thrownError * @returns {false|Object} リトライしない場合はfalseを返す。する場合はthis(ajaxSettingsオブジェクト)を返す */ function defaultAjaxRetryFilter(jqXHR, textStatus, thrownError) { // type===GETかつステータスコードが0(タイムアウト)または12029(IEでコネクションが繋がらない)場合にリトライする var stat = jqXHR.status; // jQuery1.9以降、GET,POSTの設定はtypeではなくmethodで指定することが推奨されているが、 // thisにはtypeにtoUpperCase()されたものが格納されている var type = this.type; if (type === 'POST' || !(stat === 0 || stat === ERROR_INTERNET_CANNOT_CONNECT)) { return false; } } // ========================================================================= // // Body // // ========================================================================= /** * 設定を格納するh5.settingsオブジェクト * * @name settings * @memberOf h5 * @namespace */ h5.u.obj.ns('h5.settings'); h5.settings = { /** * failコールバックの設定されていないDeferred/Promiseオブジェクトの共通のエラー処理 ** failコールバックが一つも設定されていないDeferredオブジェクトがrejectされたときにcommonFailHandlerに設定した関数が実行されます。 *
*
* commonFailHandlerが実行されるDeferredオブジェクトは、h5.async.deferred()で作成したDeferredオブジェクトかhifive内部で生成されているDeferredオブジェクトだけです。 * jQuery.Deferred()で生成したDeferredオブジェクトは対象ではありません。 *
** commonFailHandlerの引数と関数内のthisは通常のfailハンドラと同様で、それぞれ、rejectで渡された引数、rejectの呼ばれたDefferedオブジェクト、です。 *
** // commonFailHandlerの登録 * h5.settings.commonFailHandler = function(e) { * alert(e); * }; * * // Deferredオブジェクトの生成 * var dfd1 = h5.async.deferred(); * var dfd2 = h5.async.deferred(); * var dfd3 = h5.async.deferred(); * * dfd1.reject(1); * // alert(1); が実行される * * dfd2.fail(function() {}); * dfd2.reject(2); * // failコールバックが登録されているので、commonFailHandlerは実行されない * * var promise3 = dfd3.promise(); * promise3.fail(function() {}); * dfd3.reject(3); * // promiseオブジェクトからfailコールバックを登録した場合も、commonFailHandlerは実行されない * * h5.ajax('hoge'); * // 'hoge'へのアクセスがエラーになる場合、commonFailHandlerが実行される。 * // エラーオブジェクトが引数に渡され、[object Object]がalertで表示される。 * // h5.ajax()の戻り値であるDeferredオブジェクトが内部で生成されており、 * // そのDeferredオブジェクトにfailハンドラが登録されていないためである。 * * var d = h5.ajax('hoge'); * d.fail(function() {}); * // failハンドラが登録されているため、commonFailHandlerは実行されない ** *
* h5.settings.commonFailHandlerのデフォルト値はnullです。共通のエラー処理はデフォルトでは何も実行されません。 * commonFailHandlerでの処理を止めたい場合は、nullを代入して設定をクリアしてください。 *
* ** h5.settings.commonFailHandler = null; ** * @memberOf h5.settings * @type Function */ commonFailHandler: null, /** * コントローラ、ロジックへのアスペクトを設定します。 * * @memberOf h5.settings * @type Aspect|Aspect[] */ aspects: null, /** * ログの設定を行います。 * * @memberOf h5.settings * @type Object */ log: null, /** * コントローラのイベントリスナーのターゲット要素(第2引数)をどの形式で渡すかを設定します。
h5.settings.dynamicLoading.retryCount = 3;
のようにして設定します。* このパラメータはオブジェクトで、以下のプロパティを持ちます *
** 通信エラーが発生した場合、ここで指定した秒数待ってからリクエストを送信します。デフォルトは500msです。 * 同期(async:false)でh5.ajaxを呼んだ場合はretryIntervalは無視され、即座にリトライします。 (同期で呼んだ場合は必ず結果が同期で返ってきます。) *
** リトライが有効な場合、呼び出しが失敗した場合に呼ばれます。 (失敗か成功かは、jQuery.ajaxの結果に基づく。200番台、304番なら成功、それ以外は失敗。) *
** retryFilterに設定した関数がfalseを返した場合はリトライを中止し、それ以外を返した場合はリトライを継続します。 * (retryCountに設定した回数だけリトライをしたら、retryFilterの戻り値に関わらずリトライを中止します。) *
** デフォルトで設定してあるretryFilterは、「メソッドがGET、かつ、コネクションタイムアウトで失敗した場合のみリトライする」 ようになっています。 *
** この挙動を変えたい場合は、以下のようにしてretryFilter関数を差し替えます。 *
*
* h5.settings.ajax.retryFilter = function(jqXHR, textStatus, thrownError) {
* // この関数のthisはh5.ajax()呼び出し時のパラメータオブジェクトです。
* // thisを見ると、メソッドがGETかPOSTか、等も分かります。
* console.log('Ajax呼び出し失敗。textStatus = ' + textStatus);
* return false; // 明示的にfalseを返した場合のみリトライを中止する
* }
*
* $(document).bind('h5preinit', function() {
* h5.settings.ajax = {
* retryCount: 3,
* retryInterval: 500,
* retryFilter: function(){...}
* };
* });
*
*
* また、h5.ajax()の呼び出しパラメータで指定すると、呼び出しごとに設定を変えることもできます。 指定しなかったパラメータはh5.settings.ajax
のパラメータが使われます。
*
* h5.ajax({
* url: 'hoge',
* retryCount: 1
* });
* // この場合、retryCountだけ1になり、retryIntervalとretryFilterはsettingsのものが使われます。
*
*
* @since 1.1.5
* @memberOf h5.settings
* @type Object
*/
ajax: {
retryCount: 0,
retryInterval: 500,
retryFilter: defaultAjaxRetryFilter
},
/**
* コントローラがh5trackstartイベントにバインドするときに、バインド対象のターゲットに設定するCSSプロパティ"touch-action"の値
* * デフォルトは"none"で、h5trackstartイベントがバインドされた要素はタッチ操作でスクロールされないようになります。 *
** nullを設定した場合はtouch-actionへの値の設定は行いません。 *
* * @since 1.1.10 * @memberOf h5.settings * @type String */ trackstartTouchAction: 'none', /** * h5.resモジュールの設定 ** 以下のプロパティの設定を行ってください *
** 以下のプロパティの設定を行ってください。 *
*
* 引数に指定されたPromiseオブジェクトの挙動によって、以下のような処理を実行します。
*
* settingsに指定されたコールバックはdone,fail,alwaysで登録させる。
* success -> done, error -> fail, complete -> always にそれぞれ対応。
* (success,error,completeメソッドはjQuery1.8で非推奨になったため)。
*
* includeFunction==trueなら関数プロパティはコピーしない。promiseからコピーする関数及び非推奨な関数はコピーしない。 *
* * @private * @param {JqXHRWrapper} jqXHRWrapper * @param {Object} jqXHR コピー元のjqXHR * @param {Boolean} includeFunction 関数プロパティをコピーするかどうか(trueならコピー) * @param {Promise} promise promiseが持つプロパティはコピーしない */ function copyJqXHRProperties(jqXHRWrapper, jqXHR, includeFunction) { // jqXHRの中身をコピー // 関数プロパティならapplyでオリジナルのjqXHRの関数を呼ぶ関数にする for ( var prop in jqXHR) { // includeFunction=falseの場合は関数はコピーしない。 // includeFunction=trueの場合、 // 非推奨なプロパティ以外をコピー if (jqXHR.hasOwnProperty(prop) && (includeFunction || !isFunction(jqXHR[prop])) && $.inArray(prop, DEPRECATED_METHODS) === -1) { // 値をコピー jqXHRWrapper[prop] = jqXHR[prop]; } } } // ========================================================================= // // Body // // ========================================================================= /** * jqXHRWrapper ** このクラスは自分でnewすることはありません。 h5.ajax()の戻り値がこのクラスです。 * jqXHRをラップしているクラスで、jqXHRのメソッド、プロパティを使用することができます。 *
*jqXHRについての詳細は{@link http://api.jquery.com/jQuery.ajax/|jQuery.ajax() | jQuery API Documentation}をご覧ください。 *
** 注意:jqXHRオブジェクトと違い、success, error, complete メソッドはありません(非推奨であるため)。 それぞれ、done, * fail, always を使用して下さい。 *
* * @class * @name JqXHRWrapper */ /** * @private * @param jqXHR * @param dfd */ function JqXHRWrapper(jqXHR, dfd) { // オリジナルのjqXHRから値をコピー copyJqXHRProperties(this, jqXHR, true); // jqXHRWrapperをpromise化する // (jqXHRのdoneやfailは使用しない。promise化で上書かれる。) dfd.promise(this); // alwaysをオーバーライド // jQuery1.7.0のバグ(alwaysがjqXHRではなくpromiseを返すバグ)の対応 // http://bugs.jquery.com/ticket/10723 "JQXHR.ALWAYS() RETURNS A PROMISE INSTEAD OF A JQXHR OBJECT" var originalAlways = this.always; this.always = function(/* var_args */) { originalAlways.apply(this, arguments); return this; }; } /** * HTTP通信を行います。 ** 基本的な使い方は、jQuery.ajax()と同じです。戻り値はjqXHRをラップした * JqXHRWrapperクラスです。 *
*
* jQuery.ajax()と異なる点は共通のエラーハンドラが定義できることと、リトライオプションを指定できることです。 *
** h5.settings.commonFailHandlerに関数が設定されている場合、 * エラーコールバックが登録されないままajaxが失敗すると、h5.settings.commonFailHandlerに設定した関数が呼ばれます。 *
*
* h5.settings.ajaxでリトライをする設定がしてあれば、リトライを行います(デフォルトはリトライを行わない)。
* また、引数からもリトライの設定を指定することができ、h5.settings.ajaxの設定よりも優先します。
*
* h5.ajax({
* url: 'hoge',
* cache: false // jQuery.ajaxのオプション
* retryCount: 3, // h5.ajaxで追加しているオプション。リトライ回数。
* retryInterval: 200 // h5.ajaxで追加しているオプション。リトライ間のインターバル。
* retryFilter: function() // h5.ajaxで追加しているオプション。リトライ毎に実行される関数。
* });
*
* * リトライ回数の設定してある場合は、リトライ途中で通信に成功した場合はdoneコールバックが、 リトライ回数回リトライしても失敗した場合はfailコールバックが呼ばれます。 *
** 同期(async:false)で呼んだ時は、retryIntervalの値に関わらず即座に同期でリトライを行います。ajax通信結果は同期で返ってきます。 *
* * @param {Any} var_args jQuery.ajaxに渡す引数 * @returns {JqXHRWrapper} jqXHRWrapperオブジェクト * @name ajax * @function * @memberOf h5 */ function ajax(/* var_args */) { // $.ajax(settings)での呼び出しに統一する。 var settings = {}; var args = arguments; if (isString(args[0])) { // ajax(url,[settings]) での呼び出しなら、settings.urlを追加する。 $.extend(settings, args[1]); // 第1引数のurlがsettings.urlより優先される($.ajaxと同じ) settings.url = args[0]; } else { // 第一引数がurlでないならsettingsにsettingsをクローン $.extend(settings, args[0]); } // h5.settings.ajaxとマージ settings = $.extend({}, h5.settings.ajax, settings); // deferredオブジェクトの作成 var dfd = h5.async.deferred(); // settingsに指定されたコールバックを外して、deferredに登録する delegateCallbackProperties(settings, dfd); // $.ajaxの呼び出し。jqXHRを取得してjqXHRWrapperを作成。 var jqXHR = $.ajax(settings); // jqXHRWrapperの作成 var jqXHRWrapper = new JqXHRWrapper(jqXHR, dfd); /** * リトライ時のdoneハンドラ。 成功時はリトライせずにresolveして終了 */ function retryDone(_data, _textStatus, _jqXHR) { // jqXHRのプロパティの値をラッパーにコピーする // ラッパーは最終的なjqXHRの値を持てばいいので、resolveを呼ぶ直前で新しいjqXHRの値に変更する copyJqXHRProperties(jqXHRWrapper, _jqXHR); dfd.resolveWith(this, arguments); } /** * リトライ時のfailハンドラ。 ステータスコードを見て、リトライするかどうかを決める。 リトライしないなら、dfd.reject()を呼んで終了。 */ function retryFail(_jqXHR, _textStatus, _errorThrown) { if (settings.retryCount === 0 || settings.retryFilter.apply(this, arguments) === false) { // retryFilterがfalseを返した、 // またはこれが最後のリトライ、 // またはリトライ指定のない場合、 // rejectして終了 // jqXHRのプロパティの値をラッパーにコピー // ラッパーは最終的なjqXHRの値を持てばいいので、rejectを呼ぶ直前で新しいjqXHRの値に変更する copyJqXHRProperties(jqXHRWrapper, _jqXHR); dfd.rejectWith(this, arguments); return; } settings.retryCount--; if (this.async) { // 非同期ならretryIntervalミリ秒待機してリトライ var that = this; setTimeout(function() { $.ajax(that).done(retryDone).fail(retryFail); }, settings.retryInterval); } else { // 同期なら即リトライする // (同期で呼ばれたらリトライ指定があっても同期になるようにするためretryIntervalは無視する) $.ajax(this).done(retryDone).fail(retryFail); } } // コールバックを登録 jqXHR.done(retryDone).fail(retryFail); // 戻り値はjqXHRをラップしたjqXHRWrapperオブジェクト return jqXHRWrapper; } // ============================= // Expose to window // ============================= h5.u.obj.expose('h5', { ajax: ajax }); })(); /* ------ h5.cls ------ */ (function() { 'use strict'; //TODO _p_ (デフォルトPrefix)を持つプロパティの再定義をブロック //TODO propertyDescで、accessorの場合にbackingStoreのプロパティ名を指定できるようにする //(その場合、backingStore自体のプロパティも定義する必要有) //TODO propertyChangeイベントをあげるように //TODO propertyChangeイベントをクラスからあげられるように var ERR_CODE_NO_CLASS_DESC = 18000; var ERR_CODE_NO_CLASS_NAME = 18001; var ERR_CODE_NO_CLASS_CONSTRUCTOR = 18002; var ERR_CODE_CTOR_NOT_CHAINED = 18003; var ERR_CODE_METHOD_MUST_BE_FUNCTION = 18004; var ERR_CODE_CLASS_IS_ABSTRACT = 18005; var ERR_CODE_CANNOT_DEFINE_ROOT_CLASS_PROPS = 18006; var ERR_CODE_INVALID_NAMESPACE = 18007; var ERR_CODE_RESERVED_STATIC_PROP_NAME = 18008; var ERR_CODE_DUPLICATE_PROP = 18009; var ERR_CODE_DUPLICATE_STATIC_PROP = 18010; var ERR_CODE_STATIC_METHOD_MUST_BE_FUNCTION = 18011; //fwLoggerのメソッド呼び出しはビルド時(minify時)に呼び出しコードごと削除される var fwLogger = h5.log.createLogger('h5.cls'); /* del begin */ var FW_LOG_ISDYNAMIC_IS_OBSOLETE = '{0}: クラス定義のisDynamic指定はver.1.3.3で廃止されました。ver.1.3.3以降、全てのクラスについて、インスタンス生成時にフレームワークによるObject.seal()は行われず、動的なプロパティ追加が可能です。'; var errMsgMap = {}; errMsgMap[ERR_CODE_NO_CLASS_DESC] = 'クラス定義がありません。'; errMsgMap[ERR_CODE_NO_CLASS_NAME] = 'クラス定義にnameがありません。クラス名は必須です。'; errMsgMap[ERR_CODE_NO_CLASS_CONSTRUCTOR] = '{0}: クラスのメソッド定義にconstructorがありません。constructorは必須です。'; errMsgMap[ERR_CODE_CTOR_NOT_CHAINED] = '{0}: 親クラスのコンストラクタ呼び出しが途中で行われていません。継承関係のあるすべてのクラスのコンストラクタの先頭で親コンストラクタの呼び出しが行われていることを確認してください。'; errMsgMap[ERR_CODE_METHOD_MUST_BE_FUNCTION] = '{0}: クラス定義のmethodには関数以外は記述できません。違反しているプロパティ={1}'; errMsgMap[ERR_CODE_CLASS_IS_ABSTRACT] = '{0}: このクラスは抽象クラス(isAbstract=true)です。インスタンスを生成することはできません。'; errMsgMap[ERR_CODE_CANNOT_DEFINE_ROOT_CLASS_PROPS] = '{0}: 親クラスで定義されているプロパティは再定義できません。定義しようとしたプロパティ={1}'; errMsgMap[ERR_CODE_INVALID_NAMESPACE] = 'namespaceが指定されていません。'; errMsgMap[ERR_CODE_RESERVED_STATIC_PROP_NAME] = '静的プロパティに予約済みの名前は使用できません。名前={0}'; errMsgMap[ERR_CODE_DUPLICATE_PROP] = '{0}: クラス定義内でプロパティ名が重複しています。名前={1}'; errMsgMap[ERR_CODE_DUPLICATE_STATIC_PROP] = '{0}: クラス定義内で静的プロパティ名が重複しています。名前={1}'; errMsgMap[ERR_CODE_STATIC_METHOD_MUST_BE_FUNCTION] = '{0}: クラス定義の静的methodには関数以外は指定できません。メソッド名={1}'; addFwErrorCodeMap(errMsgMap); /* del end */ var PROPERTY_BACKING_STORE_PREFIX = '_p_'; /** * JavaScriptのFunction上の予約語 */ var JS_RESERVED_NAMES = ['length', 'name', 'displayName', 'arguments', 'prototype', 'caller']; /** * h5.clsのクラスオブジェクトの予約語 */ var H5_RESERVED_NAMES = ['extend', 'getClass']; /** * JSとh5.clsの予約語をマージした配列。この配列で定義されている名前は静的プロパティ名に使えない */ var RESERVED_STATIC_NAMES = JS_RESERVED_NAMES.concat(H5_RESERVED_NAMES); /** * 静的メンバーの名前が使用可能かどうかをチェックします。NG名だった場合は例外を投げます。 * * @param {String} name メンバー名 * @throws Error */ function validateStaticMemberName(name) { RESERVED_STATIC_NAMES.forEach(function(n) { if (name === n) { throwFwError(ERR_CODE_RESERVED_STATIC_PROP_NAME, name); } }); } function defineClass(classManager, parentClass, classDescriptor) { if (!classDescriptor) { throwFwError(ERR_CODE_NO_CLASS_DESC); } if (!classDescriptor.name) { throwFwError(ERR_CODE_NO_CLASS_NAME); } if (!classDescriptor.method || !classDescriptor.method.constructor) { throwFwError(ERR_CODE_NO_CLASS_CONSTRUCTOR, classDescriptor.name); } if (typeof classDescriptor.isDynamic !== 'undefined') { fwLogger.warn(FW_LOG_ISDYNAMIC_IS_OBSOLETE, classDescriptor.name); } var ctor = classDescriptor.method.constructor; //親クラスが指定されていれば、プロトタイプを継承する if (parentClass) { //Class._ctorは必ず存在する(constructorが必須だから) ctor.prototype = Object.create(parentClass._ctor.prototype); ctor.prototype.constructor = ctor; //ctor._super = parentClass._ctor; } else { //親クラスがない場合、何もしないコンストラクタをセット //ctor._super = function() { //do nothing //}; } var superObject = { constructor: function() { //Chrome(V8)では、Array.prototype.slice.call(arguments)でargumentsを配列化して //func.apply()の引数に使うと、プロファイラで"Not Optimized: Bad value context ..."という警告が出る。 //このようにループでコピーして呼ぶと、警告は出なくなる。 //ただし、この処理全体を別の関数にして呼び出すと再び警告が出てしまうので、 //このループは都度書く必要がある。 //また、ES2015では Array.from() が追加されたが、現時点(Chrome56)ではslice()よりも遅かった。 //またNot Optimizedの警告も出るので、採用しない。 var argsLen = arguments.length; var argsArray = new Array(argsLen); for (var i = 0; i < argsLen; i++) { argsArray[i] = arguments[i]; } return ctor.apply(this, argsArray); } }; // superObjectには、メソッドとアクセサを直接ぶら下げる(コピーする)。 // このsuperObjectは、extend(function(_super){}) のように引数で渡され、 // _super.constructor.call(this); _super.myMethod.call(this); のように // call()(またはapply())の形でのみ使用されることを意図したものである。 // アクセサについては、superObjectを対象にdefinePropertyすればよい。 // これによって、 extend()時に MyClass._super.prototype.myMethod.call(); // が _super.myMethod.call(this, xxx); にできる。 var newClass = new HifiveClass(classManager, classDescriptor, ctor, parentClass, superObject); ctor.prototype.__name = classDescriptor.name; ctor.prototype._class = newClass; // 親から辿ってデスクリプタの配列とフィールド一覧を作成する。 var classes = [{ descriptor: classDescriptor, ctor: ctor }]; var members = {}; if (parentClass) { var p = parentClass; do { classes.unshift({ descriptor: p._descriptor, ctor: p._ctor }); for ( var m in (p._descriptor.field || {})) { members[m] = 'field'; } for ( var m in (p._descriptor.accessor || {})) { members[m] = 'accessor'; } for ( var m in (p._descriptor.method || {})) { if (m !== 'constructor') { members[m] = 'method'; } } } while (p = p._parentClass); } var fieldDesc = classDescriptor.field; var accessorDesc = classDescriptor.accessor; var methodDesc = classDescriptor.method; for ( var m in methodDesc) { if (m === 'constructor') { continue; } var method = methodDesc[m]; if (typeof method !== 'function') { throwFwError(ERR_CODE_METHOD_MUST_BE_FUNCTION, [classDescriptor.name, m]); } // 重複チェック if ((typeof members[m] === 'string' && members[m] !== 'method') || (fieldDesc && m in fieldDesc) || (accessorDesc && m in accessorDesc)) { throwFwError(ERR_CODE_DUPLICATE_PROP, [classDescriptor.name, m]); } ctor.prototype[m] = method; } if (fieldDesc) { for ( var f in fieldDesc) { if ((typeof members[f] === 'string' && members[f] !== 'field') || (accessorDesc && f in accessorDesc) || (f in methodDesc)) { throwFwError(ERR_CODE_DUPLICATE_PROP, [classDescriptor.name, f]); } //インスタンスでフィールドを初期化せずに値を読みだした場合は //プロトタイプチェーンをたどって「null」が返るようにする //(プロトタイプオブジェクトでその名前のフィールドを(値=nullで)定義しておく) var fd = fieldDesc[f]; var defaultValue = null; if (fd && fd.defaultValue !== undefined) { defaultValue = fd.defaultValue; } ctor.prototype[f] = defaultValue; } } if (accessorDesc) { if ('_class' in accessorDesc) { throwFwError(ERR_CODE_CANNOT_DEFINE_ROOT_CLASS_PROPS, [classDescriptor.name, '_class']); } for ( var propName in accessorDesc) { if ((typeof members[propName] === 'string' && members[propName] !== 'accessor') || (fieldDesc && propName in fieldDesc) || (propName in methodDesc)) { throwFwError(ERR_CODE_DUPLICATE_PROP, [classDescriptor.name, propName]); } var ad = accessorDesc[propName]; if (ad) { //アクセサとしてgetter, setter関数が書いてある場合 Object.defineProperty(ctor.prototype, propName, { configurable: false, enumerable: true, get: ad.get, set: ad.set }); } else { //アクセサの定義だけがある(getter, setterの関数は書かれていない)場合は //バッキングフィールド自動定義 (function(pName) { Object.defineProperty(ctor.prototype, pName, { configurable: false, enumerable: true, get: function() { return this[PROPERTY_BACKING_STORE_PREFIX + pName]; }, set: function(value) { this[PROPERTY_BACKING_STORE_PREFIX + pName] = value; } }); var defaultValue = null; if (ad && ad.defaultValue !== undefined) { defaultValue = ad.defaultValue; } //バッキングストアは暗黙的に作られるものなので //enumerable=falseで生成する Object.defineProperty(ctor.prototype, PROPERTY_BACKING_STORE_PREFIX + pName, { writable: true, configurable: false, enumerable: false, value: defaultValue }); })(propName); } } } // SuperObjectにメソッドとアクセサをコピー classes .forEach(function(cls) { // メソッド if (cls.descriptor.method) { var methodDesc = cls.descriptor.method; for ( var m in methodDesc) { if (m !== 'constructor') { superObject[m] = methodDesc[m]; } } } // アクセサ if (cls.descriptor.accessor) { var accessorDesc = cls.descriptor.accessor; for ( var propName in accessorDesc) { (function(pName) { var wrapper = {}; var descriptor = Object.getOwnPropertyDescriptor( cls.ctor.prototype, pName); if (descriptor.get) { wrapper.get = descriptor.get; } if (descriptor.set) { wrapper.set = descriptor.set; } Object.freeze(wrapper); superObject[pName] = wrapper; })(propName); } } }); Object.freeze(superObject); // static defineStaticMembers(newClass, classDescriptor); //全てが完了したら、このクラスのマネージャにクラスを登録する(getClass()でこのクラスオブジェクトを取得できるようになる) classManager._classMap[classDescriptor.name] = newClass; return newClass; } /** * 静的メンバーを定義します。 * * @private * @param cls HifiveClassクラスオブジェクト * @param descriptor クラスディスクリプタ */ function defineStaticMembers(cls, descriptor) { var fieldDescriptor = descriptor.staticField || {}; var accessorDescriptor = descriptor.staticAccessor || {}; var methodDescriptor = descriptor.staticMethod || {}; var names = Object.keys(fieldDescriptor).concat(Object.keys(accessorDescriptor)).concat( Object.keys(methodDescriptor)); // 名前チェック names.forEach(function(name) { validateStaticMemberName(name); }); // 重複チェック var length = names.length; for (var i = 0; i < length - 1; i++) { for (var j = i + 1; j < length; j++) { if (names[i] === names[j]) { throwFwError(ERR_CODE_DUPLICATE_STATIC_PROP, [descriptor.name, names[i]]); } } } // 定義 defineStaticFields(cls, fieldDescriptor); defineStaticAccessors(cls, accessorDescriptor); defineStaticMethods(cls, methodDescriptor); Object.seal(cls.statics); } /** * 静的フィールドを定義します。 * * @private * @param cls HifveClassクラスオブジェクト * @param fieldDescriptor フィールドディスクリプタ */ function defineStaticFields(cls, fieldDescriptor) { Object.keys(fieldDescriptor).forEach(function(name) { var field = fieldDescriptor[name]; var defaultValue = field && 'defaultValue' in field ? field.defaultValue : null; var readOnly = field && field.isReadOnly === true; Object.defineProperty(cls._ctor, name, { enumerable: true, configurable: false, value: defaultValue, writable: !readOnly }); var compatDescriptor = { enumerable: true, configurable: false, get: function() { return cls._ctor[name]; } }; if (!readOnly) { compatDescriptor.set = function(val) { cls._ctor[name] = val; }; } Object.defineProperty(cls.statics, name, compatDescriptor); }); } /** * 静的アクセサを定義します。 * * @private * @param cls HifiveClassクラスオブジェクト * @param accessorDescriptor アクセサディスクリプタ */ function defineStaticAccessors(cls, accessorDescriptor) { Object.keys(accessorDescriptor).forEach(function(name) { var accessor = accessorDescriptor[name]; var descriptor = { enumerable: true, configurable: false }; if (!accessor) { var backing = null; descriptor.get = function() { return backing; }; descriptor.set = function(val) { backing = val; }; } else { descriptor.get = accessor.get; descriptor.set = accessor.set; } Object.defineProperty(cls._ctor, name, descriptor); Object.defineProperty(cls.statics, name, descriptor); }); } /** * 静的メソッドを定義します。 * * @private * @param cls HifiveClassクラスオブジェクト * @param methodDescriptor メソッドディスクリプタ */ function defineStaticMethods(cls, methodDescriptor) { Object.keys(methodDescriptor).forEach(function(name) { var method = methodDescriptor[name]; if (typeof method !== 'function') { throwFwError(ERR_CODE_STATIC_METHOD_MUST_BE_FUNCTION, [cls.getFullName(), name]); } var descriptor = { enumerable: false, configurable: false, value: method, writable: false }; Object.defineProperty(cls._ctor, name, descriptor); Object.defineProperty(cls.statics, name, descriptor); }); } function HifiveClass(classManager, classDescriptor, ctor, parentClass, superObject) { this._descriptor = classDescriptor; this._ctor = ctor; this._parentClass = parentClass; this._isCtorChained = false; this._manager = classManager; this._superObject = superObject; Object.defineProperty(this, 'statics', { value: {}, writable: false }); } $.extend(HifiveClass.prototype, { extend: function(classDescriptor) { var clsDesc = classDescriptor; if (typeof classDescriptor === 'function') { clsDesc = classDescriptor(this._superObject); } var subClass = defineClass(this._manager, this, clsDesc); return subClass; }, create: function() { if (this._descriptor.isAbstract === true) { throwFwError(ERR_CODE_CLASS_IS_ABSTRACT, this._descriptor.name); } var instance = Object.create(this._ctor.prototype); //ver.1.3.2まで、クラス定義にisDynamic: false(デフォルト:false)を指定すると、 //インスタンス生成時にクラス定義で書かれているフィールドを //インスタンスにown-propertyとして作成し、各インスタンスに対してObject.seal()していた。 //しかし、この処理に時間がかかるため、数が増えるとインスタンスの生成だけで //ある程度の時間がかかるようになってしまっていた。(特にIE11とFirefoxで速度が劣化する) //そのため、ver.1.3.3にて仕様変更し、isDynamicの設定は削除し、かつ、 //インスタンス生成時にはフィールドの初期化は行わないようにした。 //ただしこのため、isDynamic=trueの場合、一度も書き込みしていないフィールドは // instance.hasOwnProperty('fieldName')がfalseになる、という副作用がある。 // (for-inループの場合は、prototypeオブジェクトにフィールドを定義してあるので列挙される // (自動生成されたアクセサ用バッキングストアを除く)) //argumentsを配列化する処理はこのforループでこの場所で行うこと。 //詳細はsuperObjectのconstructor参照。 var argsLen = arguments.length; var argsArray = new Array(argsLen); for (var i = 0; i < argsLen; i++) { argsArray[i] = arguments[i]; } //コンストラクタ実行 this._ctor.apply(instance, argsArray); if (this._isCtorChained === false) { //クラスのコンストラクタ実行後、RootClassのコンストラクタまで実行されていない //= _super.constructor.call();が途中で途切れている場合はエラーにする throwFwError(ERR_CODE_CTOR_NOT_CHAINED, this._descriptor.name); } return instance; }, getDescriptor: function() { return this._descriptor; }, getParentClass: function() { return this._parentClass; }, getFullName: function() { return this._descriptor.name; }, isClassOf: function(obj) { var ret = obj instanceof this._ctor; return ret; }, getManager: function() { return this._manager; } }); /** * ルートクラス定義。 * * @private */ var ROOT_CLASS_DESC = { name: 'h5.cls.RootClass', method: { constructor: function HifiveRootObject() { //ルートクラスなので、"super.constructor.call()"は不要 this._class._isCtorChained = true; }, getClass: function() { return this._class; } } }; function HifiveClassManager() { this._rootClass = null; // クラス名(FQCN) -> クラスオブジェクト のマップ this._classMap = {}; this._rootClass = defineClass(this, null, ROOT_CLASS_DESC); } $.extend(HifiveClassManager.prototype, { /** * このマネージャで管理されているルートクラスを取得します。 * * @memberOf h5.cls.HifiveClassManager */ getRootClass: function() { return this._rootClass; }, /** * 指定された名前のクラスオブジェクトを取得します。戻り値のクラスのインスタンスを生成する場合は ret.create() のようにします。 * * @param fqcn 完全修飾クラス名 * @returns {HifiveClass} クラスオブジェクト */ getClass: function(fqcn) { var cls = this._classMap[fqcn]; //undefinedではなくnullを返す(設計ポリシー) cls = cls === undefined ? null : cls; return cls; }, /** * 指定された名前空間に属するクラスをそのクラス名をキーとして保持するオブジェクトを返します。 * * @param {String} namespace 名前空間 * @returns 名前空間オブジェクト */ getNamespaceObject: function(namespace) { if (!namespace) { throwFwError(ERR_CODE_INVALID_NAMESPACE); } var ns = namespace + '.'; var ret = {}; var classMap = this._classMap; var namespaceLen = ns.length; for ( var fqcn in classMap) { if (fqcn.lastIndexOf(ns, 0) === 0 && fqcn.indexOf('.', namespaceLen) === -1) { var simpleName = fqcn.substr(namespaceLen); ret[simpleName] = classMap[fqcn]; } } return ret; } }); var defaultClassManager = new HifiveClassManager(); var RootClass = defaultClassManager.getRootClass(); h5.u.obj.expose('h5.cls', { manager: defaultClassManager, RootClass: RootClass }); })(); /* ------ h5.event ------ */ (function() { 'use strict'; var ERR_CODE_PROPERTY_NAME_IS_REQUIRED = 90001; //TODO 要エラーコード採番 var RootClass = h5.cls.RootClass; /** * イベントディスパッチャ ** イベントの発火・リスナ管理を行うクラスです。 *
* * @mixin * @name EventDispatcher * @param super_ スーパーオブジェクト * @returns {EventDispatcherClass} イベントディスパッチャクラス */ RootClass.extend(function(super_) { /** * 受け取ったオブジェクトをイベントオブジェクトにする * * @private * @param {Object} event 任意のオブジェクト * @param {Object} target event.targetになるオブジェクト * @returns {Object} イベントオブジェクト */ function setEventProperties(event, target) { // ターゲットの追加 if (!event.target) { event.target = target; } // タイムスタンプの追加 if (!event.timeStamp) { event.timeStamp = new Date().getTime(); } } var desc = { name: 'h5.event.EventDispatcher', field: { _eventListeners: null }, method: { /** * @mebmerOf h5.event.EventDispatcher */ constructor: function EventDispatcher() { super_.constructor.call(this); this._eventListeners = {}; }, /** * イベントリスナが登録されているかどうかを返します ** 第一引数にイベント名、第二引数にイベントリスナを渡し、指定したイベントに指定したイベントリスナが登録済みかどうかを返します。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {String} type イベント名 * @param {Function} listener イベントリスナ * @returns {Boolean} 第一引数のイベント名に第二引数のイベントリスナが登録されているかどうか */ hasEventListener: function(type, listener) { if (!this._eventListeners) { return false; } var l = this._eventListeners[type]; if (!l || !this._eventListeners.hasOwnProperty(type)) { return false; } for (var i = 0, count = l.length; i < count; i++) { if (l[i] === listener) { return true; } } return false; }, /** * イベントリスナを登録します。 ** 第一引数にイベント名、第二引数にイベントリスナを渡し、イベントリスナを登録します。指定したイベントが起こった時にイベントリスナが実行されます。 *
** イベントリスナは、関数またはEventListener * インタフェースを実装するオブジェクト(handleEventプロパティに関数を持つオブジェクト)で指定できます。 *
* 指定したイベントに、指定したイベントリスナが既に登録されていた場合は何もしません。 *
** 同一のイベントに対して複数回addEventListener()を呼び、複数のイベントリスナを登録した場合は、イベント発火時に登録した順番に実行されます。 *
** 第3引数以降が指定されていても無視されます。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {String} type イベント名 * @param {Function|Object} listener イベントリスナまたはhandleEventを持つイベントリスナオブジェクト */ addEventListener: function(type, listener) { // 引数チェック // typeは文字列で、第2引数まで指定されていることをチェックする // listenerが関数またはイベントリスナオブジェクトかどうかは、実行時に判定し、関数でもイベントリスナオブジェクトでもない場合は実行しない if (arguments.length < 2 || !isString(type)) { throwFwError(ERR_CODE_INVALID_ARGS_ADDEVENTLISTENER); } if (listener == null || this.hasEventListener(type, listener)) { // nullまたはundefinedが指定されている、または既に登録済みのイベントリスナなら何もしない return; } if (!(this._eventListeners.hasOwnProperty(type))) { this._eventListeners[type] = []; } this._eventListeners[type].push(listener); }, /** * イベントリスナを削除します。 ** 第一引数にイベント名、第二引数にイベントリスナを渡し、指定したイベントから指定したイベントリスナを削除します。 *
** 指定したイベント名に指定したイベントリスナが登録されていない場合は何もしません。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {String} type イベント名 * @param {Function} listener イベントリスナ */ removeEventListener: function(type, listener) { if (!this.hasEventListener(type, listener)) { return; } var l = this._eventListeners[type]; for (var i = 0, count = l.length; i < count; i++) { if (l[i] === listener) { l.splice(i, 1); return; } } }, /** * イベントをディスパッチします ** イベントオブジェクトを引数に取り、そのevent.typeに登録されているイベントリスナを実行します。 * イベントオブジェクトにpreventDefault()関数を追加してイベントリスナの引数に渡して呼び出します。 *
** 戻り値は『イベントリスナ内でpreventDefault()が呼ばれたかどうか』を返します。 *
* * @since 1.1.0 * @memberOf EventDispatcher * @param {Object} event イベントオブジェクト * @returns {Boolean} イベントリスナ内でpreventDefault()が呼ばれたかどうか。 */ dispatchEvent: function(event) { var l = this._eventListeners[event.type]; if (!l) { return; } // リスナをslice(0)して、dispatchEventを呼んだ瞬間にどのリスナが呼ばれるか確定させる // (あるイベントのイベントリスナの中でadd/removeEventListenerされても、そのイベントが実行するイベントリスナには影響ない) l = l.slice(0); setEventProperties(event, this); // リスナーを実行。stopImmediatePropagationが呼ばれていたらそこでループを終了する。 for (var i = 0, count = l.length; i < count && !event.isImmediatePropagationStopped(); i++) { if (isFunction(l[i])) { l[i].call(event.target, event); } else if (l[i].handleEvent) { // イベントリスナオブジェクトの場合はhandleEventを呼ぶ // handleEvent内のコンテキストはイベントリスナオブジェクトなので、callは使わずにそのまま呼び出す l[i].handleEvent(event); } } return event.defaultPrevented; } } }; return desc; }); var Event = RootClass.extend(function(super_) { var desc = { name: 'h5.event.Event', field: { target: null, timeStamp: null, _type: null, _defaultPrevented: null, _isImmediatePropagationStopped: null }, accessor: { type: { get: function() { return this._type; } }, defaultPrevented: { get: function() { return this._defaultPrevented; } } }, method: { /** * @memberOf h5.event.Event */ constructor: function Event(type) { super_.constructor.call(this); this._type = type; this._defaultPrevented = false; this._isImmediatePropagationStopped = false; }, preventDefault: function() { this._defaultPrevented = true; }, isDefaultPrevented: function() { return this._defaultPrevented; }, stopImmediatePropagation: function() { this._isImmediatePropagationStopped = true; }, isImmediatePropagationStopped: function() { return this._isImmediatePropagationStopped; } } }; return desc; }); Event.extend(function(super_) { var EVENT_NAME_PROPERTY_CHANGE = 'propertyChange'; var desc = { name: 'h5.event.PropertyChangeEvent', field: { _propertyName: null, _oldValue: null, _newValue: null }, accessor: { propertyName: { get: function() { return this._propertyName; } }, oldValue: { get: function() { return this._oldValue; } }, newValue: { get: function() { return this._newValue; } } }, method: { constructor: function PropertyChangeEvent(propertyName, oldValue, newValue) { super_.constructor.call(this, EVENT_NAME_PROPERTY_CHANGE); if (propertyName == null) { throwFwError(ERR_CODE_PROPERTY_NAME_IS_REQUIRED); } this._propertyName = propertyName; this._oldValue = oldValue !== undefined ? oldValue : null; this._newValue = newValue !== undefined ? newValue : null; } } }; return desc; }); })(); /* ------ h5.res ------ */ (function() { // ========================================================================= // // Constants // // ========================================================================= // ============================= // Production // ============================= /** * リソースファイルの取得時に発生するエラー */ var ERR_CODE_RESOURCE_AJAX = 17000; /** * タイムアウト時のエラー */ var ERR_CODE_RESOLVE_TIMEOUT = 17001; // ---------- EJSResolverのエラー ---------- /** テンプレートファイルの内容読み込み時に発生するエラー */ var ERR_CODE_TEMPLATE_FILE_NO_TEMPLATE = 7001; /** テンプレートIDが不正である時に発生するエラー */ var ERR_CODE_TEMPLATE_INVALID_ID = 7002; /** テンプレートファイルの取得時に発生するエラー */ var ERR_CODE_TEMPLATE_AJAX = 7003; /** テンプレートファイルにscriptタグ以外の記述がある */ var ERR_CODE_TEMPLATE_FILE_INVALID_ELEMENT = 7011; // ============================= // Development Only // ============================= var fwLogger = h5.log.createLogger('h5.res'); /* del begin */ /** * 各エラーコードに対応するメッセージ */ var errMsgMap = {}; errMsgMap[ERR_CODE_RESOURCE_AJAX] = 'リソースファイルを取得できませんでした。ステータスコード:{0}, URL:{1}'; // EJSResolverのエラー errMsgMap[ERR_CODE_TEMPLATE_FILE_NO_TEMPLATE] = 'テンプレートファイルに