[JavaScript] new ActiveXObject (progId)っぽく

IEのJavaScriptで

var cn = new ActiveXObject("ADODB.Connect");

みたいなことが出来たので、

Chromeでもできないのか考えてみた。

ActiveXObjectクラスのnew演算子でパラメータをplogIdとするOCXっぽいオブジェクトが戻って

くれれば嬉しいので、ストレートに

returnを使ってみた。

class ActiveXObject {
    constructor(className, json)
    {
        this.domain       = "xxx.xxx.xxx"; // この辺はC#側の設定を転記
        this.port         = "xxxx";         // この辺はC#側の設定を転記
        this.className    = className;
        this.objectID     = null;
        // 派生クラスの場合は再帰してしまうので、処理を省略
        if(typeof(className) !== 'undefined')
        {
            // classNameから当該クラスのJavaScript側クラスのオブジェクトを作成
            let cf = new Function('className', `return new ${className.replace('.', '_')}()`);
            let obj = undefined;
            try {
                obj = cf(className);
            } catch (ex) {
                throw new Error(`★new ActiveXObject("${className}")未定義クラス`);
            }
            // 初期設定値(json)があれば、設定する
            if (typeof (json) !== 'undefined') {
                obj['objectID'] = json['objectID'];
                obj.__setup__(json);
            }
            // 無ければ, C#のサーバに問い合わせる。
            else
            {
                // サーバー側にnewしたことを通知
                obj.init();
                // 放置してもサーバ側のオブジェクトは解放されないので、オブジェクトが不要になったら、適宜termメソッドを呼び出す事
                // this.term();
            }
            return obj; // new の戻り値
        }
    }
    // 設定する
    __setup__(json) {
        // プロパティにコピー
        for (let key of this.propertyNames) {
            let jj = json[key]
            if (typeof(jj) !== "undefined") {
                this[`_${key}`] = json[key];
            }
        }
        // プロパティ(コレクション)にコピー
        for (let key in this.propertyCollections) {
            let jj = json[key]
            if (typeof(jj) !== "undefined") {
                if (typeof (this[`_${key}`]) === "undefined" || this[`_${key}`] === null) {
                    this[`_${key}`] = {};
                }
                for (let idx in jj) {
                    let k = jj[idx].Name;
                    this[`_${key}`][`${k}`] = jj[idx];
                }
                // this[key]['Item'] = this[key]
            }
        }
    }
    // サーバー側にnewしたことを通知
    async init() {
        let json = await ActiveXObject.staticSendMessage('POST', this.domain, this.port, `ActiveXObject/${this.className}`);
        ActiveXObject.staticInit(this, json);
    }
    // オブジェクトの解放
    async term() {
        return await ActiveXObject.staticSendMessage('DELETE', this.domain, this.port, `ActiveXObject/${this.objectID}`);
    }
    // サーバー側にnewしたことを通知
    static staticInit(obj, json)
    {
        // 必須
        if (typeof (json["objectID"]) === 'Nothing')
        {
            alert("objectIDが取得できません。");
        }
        obj.objectID = json["objectID"];
        if (typeof (obj.onobjectID) !== 'undefined')
        {
            obj.onobjectID();
        }
        obj.SetProperties(json['activeXobject']);
        //
    }
    // 送信
    static staticSendMessage(method, domain, port, page, data)
    {
        let promise = new Promise( (resolve, reject) => {
            try {
                let parserFunction = this.parserFunction;
                let xhr = new XMLHttpRequest();
          // httpsにした方がいいのかもしれない
                let url = encodeURI(`http://${domain}:${port}/${page}`);
                xhr.open(method, url, true);
                xhr.responseType = 'text';
                xhr.send(this.staticEncodeHTMLForm(data));
                xhr.onload = function(e) {
                    if (xhr.readyState == 4) {
                        if (xhr.status == 200 ) {
                            try {
                                let json = xhr.response;
                                json = JSON.parse(json, parserFunction);
                                resolve(json);
                                return;
                            } catch (ex) {
                                console.log(`'${ex.toString()}'\n url:http://${domain}:${port}/${url}\n json:'${xhr.response}'`);
                            }
                        }
                    }
                };
            }
            catch (ex) {
                reject(ex);
                return;
            }
        });
        return promise;
    }
    // データをBODYの形式に変換
    static staticEncodeHTMLForm(data) {
        if (typeof(data) === 'undefined' || data === null)
        {
            return null;
        }
        let a = [];
        for (let aa of data) {
            a.push(aa);
        }
        return "params=" + JSON.stringify(a);
    }
    //
    static staticMakeUrlParameters(arg) {
        let rc = [];
        for (let idx = 1; idx <= arg.length; idx++) {
            rc.add(`p${idx}=JSON.stringify(${arg[idx - 1]})`);
        }
        return rc.join('&');
    }
    //
    static staticParserFunction(k,v)
    {
        try
        {
            if (typeof(v) === "string" && ((v.indexOf("function") === 0) || (v.indexOf("async") === 0)))
            {
                return eval(`(${v})`);
            }
            else
            {
                return v;
            }
        } catch (ex) {
            console.log(ex.toString());
        }
    }
}

newで使用する各ActiveXObjectのテンプレ・クラスは・・・

//  ADODB_Connection クラス
class ADODB_Connection extends ActiveXObject {
    constructor() {
        // とりあえず定番
        super();
        this.className = this.constructor.name.replace('_', '.');
        // プロパティ
        // 名前リストからプロパティを作成
        this.propertyNames = ['Attributes', 'CommandTimeout', 'ConnectionString', 'ConnectionTimeout', 'CursorLocation', 'DefaultDatabase', 'IsolationLevel', 'Mode', 'Provider', 'State', 'Version'];
        this.makeProperties(this.propertyNames);
        // プロパティ(コレクション)
        // 名前リストからダミーのメソッド(コレクション)を作成
        this.propertyCollections = {
            // https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/errors-collection-properties-methods-and-events
            'Errors': {
                'properties': ['Count', 'Item'],
                'methods': ['Clear', 'Refresh'],
                'events': []
            },
            // https://docs.microsoft.com/ja-jp/office/client-developer/access/desktop-database-reference/properties-collection-properties-methods-and-events
            'Properties': {
                'properties': ['Count', 'Item'],
                'methods': ['Refresh'],
                'events': []
            }
        };
        this.makePropertiesCollection(this.propertyCollections);
        // メソッド
        // 名前リストからダミーのメソッドを作成
        this.methodNames = ['BeginTrans', 'CommitTrans', 'RollbackTrans', 'Cancel', 'Close', 'Execute', 'Open', 'OpenSchema'];
        this.makeMethods(this.methodNames);
        // イベント
        // 名前リストからダミーのメソッドを作成
        this.eventNames = ['BeginTransComplete', 'CommitTransComplete', 'RollbackTransComplete', 'ConnectComplete', 'Disconnect', 'ExecuteComplete', 'InfoMessage', 'WillConnect', 'WillExecute'];
        this.makeEvents(this.eventNames);
    }
}

“ADODB.Connection”というクラス名に使えなかいので、”ADODB_Connection”にしている。

makeXXXXは使いまわすのでActiveXObjectクラスで実装

    //
    /**
     * 名前リストからプロパティを作成
     * 
     * @param {String[]} propertyNames   名前リスト
     */
    makeProperties(propertyNames) {
        for (let propName of propertyNames) {
            Object.defineProperty(this, propName, {
                get() {
                    return this[`_${propName}`];
                },
                set(value) {
                    this[`_${propName}`] = value;
                }
            });
        }
    }
    /**
     * 名前リストからメソッドを作成
     * 
     * @param {String[]} methodNames   名前リスト
     */
    makeMethods(methodNames) {
        for (let methodName of methodNames) {
            this[methodName] = function () {
                let args = arguments;
                if (this.objectID == null) {
                    return new Promise(async (resolve, reject) => {
                        this.onobjectID = async function () {
                            let rc = await this[methodName].apply(this, args);
                            resolve(rc);
                            return;
                        }
                    });
                } else {
                    let path = `ActiveXObject/${this.objectID}/${methodName}`;
                    return new Promise(async (resolve, reject) => {
                        let json = await ActiveXObject.staticSendMessage('PUT', this.domain, this.port, path, args);
                        // エラーがあれば返す。
                        if (typeof (json['error']) !== 'undefined') {
                            reject(json['error']);
                            return;
                        }
                        //
                        this.SetProperties(json['activeXObject']);
                        //
                        let rc = json['returnValue'];
                        if (rc != null && rc["__constructor_name__"]) {
                            let className = rc["__constructor_name__"];
                            delete rc["__constructor_name__"];
                            let obj = new ActiveXObject(className, rc);
                            resolve(obj);
                            return;
                        }
                        resolve(rc);
                        return;
                    });
                }
            };
        }
    }
    /**
     * 名前リストからプロパティ(コレクション)を作成
     * 
     * @param {String[]} propertyCollectionNames   名前リスト
     */
    makePropertiesCollection(propertyCollections) {
        for (let collectionName in propertyCollections) {
            if (typeof (this[collectionName]) === "undefined") {
                this[collectionName] = {};
            }
            let co = this[collectionName];
            // コレクション本体
            Object.defineProperty(this, collectionName, {
                get() {
                    return this[`_${collectionName}`];
                },
                set(value) {
                    this[`_${collectionName}`] = value;
                }
            });
            // コレクションの設定内容
            let collection = propertyCollections[collectionName];
            // コレクションのプロパティ
            // property
            for (let propertyName of collection.properties) {
                Object.defineProperty(co, propertyName, {
                    get() {
                        return co[`_${propertyName}`];
                    },
                    set(value) {
                        co[`_${propertyName}`] = value;
                    }
                });
            }
            // コレクションのメソッド
            for (let methodName of collection.methods) {
                if (typeof (co[methodName]) === "undefined")
                {
                    co[methodName] = function () {
                        let args = arguments;
                        if (this.objectID == null) {
                            this.onobjectID = async function () {
                                let rc = await co[methodName].apply(co, args);
                                resolve(rc);
                                return;
                            }
                        } else {
                            let path = `ActiveXObject/${this.objectID}/${collectionName}/${methodName}`;
                            return new Promise(async (resolve, reject) => {
                                let json = await ActiveXObject.staticSendMessage('PUT', this.domain, this.port, path, args);
                                // エラーがあれば返す。
                                if (typeof (json['error']) !== 'undefined') {
                                    reject(json['error']);
                                    return;
                                }
                                //
                                this.SetProperties(json['activeXObject']);
                                //
                                let rc = json['returnValue'];
                                if (rc != null && rc["__constructor_name__"]) {
                                    let className = rc["__constructor_name__"];
                                    delete rc["__constructor_name__"];
                                    let obj = new ActiveXObject(className, rc);
                                    resolve(obj);
                                    return;
                                }
                                resolve(rc);
                                return;
                            });
                        }
                    };
                }
            }
            // event 未定
            //
        }
    }

コレクションはプロパティとメソッドのソースをマージした感じで雑になっている。

これで準備が整ったところで、

new ActiveXObject("ADODB.Connection");

すれば、いいはず。

これって、new 演算子をオーバーライドした事になるのかな?(知らんけど

真っ先にプロパティ・チェックするケースは見当たらなかったので、

最初にメソッド呼び出し時に、C#側からobjectIDが送られるまで待って処理する様にしている。

プロパティ参照時もobjectIDが送られるまで待って処理を入れた方がいいけど、

var table_name = schema.Fields["TABLE_NAME"].Value;

の様に呼び出されると、await が入れにくい。

うまくawaitが入っても、絶望的に読みにくくなりそう。

var table_name = await (await (await schema.Fields)["TABLE_NAME"]).Value;




コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA