1. 程式人生 > 其它 >ASP.NET AJAX(13)__利用Microsoft AJAX Library開發客戶端元件Sys.Component成員Sys.IDisposable成員Sys.INotifyDisposin

ASP.NET AJAX(13)__利用Microsoft AJAX Library開發客戶端元件Sys.Component成員Sys.IDisposable成員Sys.INotifyDisposin

Microsoft AJAX Library定義了一個客戶端元件的模型,它的基類是Sys.Component,它實現了三個介面Sys.IDisposable,Sys.INotifyDisposing,Sys.INotifyPropertyChange

Sys.Component成員

  • get_events()
  • get_id();
  • set_id();
  • get_isInitialized();
  • initialize();
  • dispose();
  • raisePropertyChanged();

Sys.IDisposable成員

  • dispose();

Sys.INotifyDisposing成員

  • add_disposing();
  • remove_disposing();

Sys.INotifyPropertyChange成員

  • add_propertyChanged();
  • remove_propertyChanged();

可視元件和不可視元件

可視元件,就是對DOM進行了封裝,在Microsoft AJAX Library中可分為兩種Sys.UI.Control和Sys.UI.Behavior,不可視元件不繼承於Control和Behavior,它是一種輔助物件

Control和Behavior

  • Sys.UI.Control:封裝了DOM元素,概念上為一個組合的控制元件
  • Sys.UI.Behavior:擴充套件了DOM元素,為DOM元素提供了額外的功能

全域性容器

  • Sys._Application為一個全域性的容器類
  • 維護著全域性的所有Component物件的生命週期

客戶端生命週期

這裡的宣告週期,很像我們的c#語言,實際上,它就是按照這種高階語言的宣告週期來開發的,如果我們要建立物件,需要在Sys.Application.init事件中建立,並且呼叫Component的initialize方法,這樣在load事件中,就可以在程式碼中控制它,這以為著,在Sys.Application的load階段,所有的元件已經必須準備好

一個客戶端與元件生命週期的示例

首先建立一個名為SimpleComponent.js的檔案

/// <referenct name="MicrosoftAjax.js"/>

Type.registerNamespace("Demo");//註冊一個名稱空間

Demo.SimpleComponent = function() {
    Demo.SimpleComponent.initializeBase(this);//呼叫父類的建構函式
}

Demo.SimpleComponent.prototype =//新增成員
{
    initialize: function() {
        Demo.SimpleComponent.callBaseMethod(this, "initialize"); //呼叫父類名為initialize的方法
        alert("I've been initialized!");
    },
    dispose: function() {
        alert("I'm being disposed!");
        Demo.SimpleComponent.callBaseMethod(this, "dispose");
    },
    sayHi: function() {
        alert("Hello,i'm you first component!");
    }
}

Demo.SimpleComponent.registerClass("Demo.SimpleComponent", Sys.Component);//註冊這個類,繼承自Sys.Component

然後建立一個aspx頁面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SimpleLifeCycle.aspx.cs" Inherits="Demo12_SimpleLifeCycle" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Demo12/SimpleComponent.js" />
            </Scripts>
        </asp:ScriptManager>
        
        <script language="javascript" type="text/javascript">
            function pageInit() {
                alert("Page is initializaing!");
                $create(//Microsoft AJAX Library提供的建立物件的簡單方法
                    Demo.SimpleComponent,//第一個引數,構造元件的型別
                    { "id": "simpleComponent" },//第二個引數,設定屬性,這裡只設置一個id
                    { "disposing": onDisposing });//第三個引數,設定事件
            }

            function pageLoad() {//頁面載入完成後被呼叫
                var simple = $find("simpleComponent"); //找到simpleComponent這個元件
                simple.sayHi();//呼叫元件的sayHi方法
            }
            
            function onDisposing() {//Component釋放的時候被呼叫
                alert("Component is being disposed");
            }

            Sys.Application.add_init(pageInit); //把pageInit方法新增到Sys.Application的init事件中,這樣在Sys.Application的init事件中方法被呼叫
        </script>
    </form>
</body>
</html>

我們開啟這個頁面,一步一步的觀察呼叫步驟

1.Sys.Application.init事件被觸發

2.Component的initialize被呼叫

3.pageLoad被呼叫Component的sayHi方法被執行

4.離開頁面,元件的dispose方法被呼叫

5.我們已經在建立物件的時候響應了物件的disposing事件,onDisposing方法被執行

開發一個Component

  • Sys.Component類(非必須)
  • 在建構函式裡定義私有變數(將變數設定為預設值)
  • 覆蓋initialize方法,初始化所有私有變數
  • 覆蓋dispose方法,釋放所有私有變數,避免資源洩漏
  • 定義其他成員
一個簡單的Component的示例

建立一個名為Timer.js的檔案

Type.registerNamespace("Demo");

Demo.Timer = function() {
    Demo.Timer.initializeBase(this);//呼叫基類建構函式
    this._interval = 1000;//私有變數設定為預設值
    this._timer = null; //私有變數設定為預設值
}

Demo.Timer.prototype =
{
    get_interval: function() {
        return this._interval;
    },
    set_interval: function(value) {
        if (this._interval != value) {//判斷現在已有interval的值,是否等於要設定的值
            this._interval = value;
            this.raisePropertyChanged("interval");//告訴外接interval屬性被改變
            if (this._timer) {
                this.stop();
                this.start();
            }
        }
    },
    add_tick: function(handler) {
        this.get_events().addHandler("tick", handler);//新增一個事件tick為事件名
    },
    remove_tick: function(handler) {
        this.get_events().removeHandler("tick", handler); //去除一個事件tick為事件名
    },
    _timerCallback: function() {
        var handler = this.get_events().getHandler("tick");//得到我們設定的事件
        if (handler) {//如果這個事件存在
            handler(this, Sys.EventArgs.Empty);
        }
    },
    start: function() {
        if (this._interval > 0) {
            this._timer = window.setInterval(//這裡使用的是javascript的原生方法
                Function.createDelegate(this, this._timerCallback), //使_timerCallback的指標指向元件本身,而不是window
                this._interval);
        }
    },
    stop: function() {
        if (this._timer) {//如果_timer已經被賦值,證明已經開始啦
            window.clearInterval(this._timer);
            this._timer = null;
        }
    }
}

Demo.Timer.registerClass("Demo.Timer", Sys.Component);

建立一個aspx頁面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Timer.aspx.cs" Inherits="Demo12_Timer" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Demo12/Timer.js" />
            </Scripts>
        </asp:ScriptManager>
        
        Interval:
        <input type="text" value="1000" id="txtInterval" />
        <input type="button" value="Change" onclick="changeInterval()" /><br />
        
        <div id="display"></div>
        
        <script language="javascript" type="text/javascript">
            Sys.Application.add_init(function() {
            $create(Demo.Timer,//建立這個Component
                { "id": "timer" },//定義一個ID
                { "tick": onTick, "propertyChanged": onPropertyChanged });//建立事件處理程式
            });

            function onPropertyChanged(sender, args) {//propertyChanged被觸發的時候,此方法被呼叫
                var property=args.get_propertyName();//得到改變屬性的名稱
                var value = sender["get_" + property].apply(sender);//apply指定指標指向元件物件
                alert(property+" is changed to "+value);
            }
            
            window.counter = 0;//一個計數器

            function onTick() {//元件的tick事件被觸發時,此方法被呼叫
                $get("display").innerHTML = window.counter++;
            }

            function pageLoad() {//頁面載入完成,timer開始工作
                $find("timer").start();
            }

            function changeInterval() {
                $find("timer").set_interval(
                    parseInt($get("txtInterval").value, 10));
            }
        </script>
    </form>
</body>
</html>

這裡就使用到了我們建立的Component,實現一個計數器的效果,類似一個客戶端的Timer

Sys.Component成員

  • events只讀屬性//事件集合
  • id屬性//元件的id
  • initialize方法
  • isInitialized只讀屬性//是否在構造中
  • raisePropertyChanged方法//告訴外界哪個屬性改變
  • propertyChanged事件//屬性改變後觸發
  • dispose方法
  • disposing事件
  • beginUpdate方法//開始Update
  • isUpdating只讀屬性//是否處於Update狀態
  • endUpdate方法
  • updated方法

元件處於正在更新的狀態稱為Update狀態,處於更新狀態時候元件的資料可能出於不一致的狀態,因此,出於更新狀態的元件,允許元件處於不一直的狀態,但是應該儘量避免與外接的交換,尤其是處於DOM元素有關的互動,有時候,合理的利用Update狀態也能夠在一定程式上提高效能

Update狀態的使用

  • Sys.Component._setProperties方法:批量修改元件的屬性(在非Update狀態下)(呼叫beginUpdate方法->設定元件屬性->呼叫endUpdate方法)

Update狀態在系統中的使用

windows的DOM事件load被觸發

           Sys.Application物件的beginCreatComponent方法被呼叫

           SysApplication物件的Init事件被觸發

                   $Creat方法被呼叫,用於建立物件

                        元件的beginUpdate方法被呼叫

                        部分元件的endUpdate方法被呼叫

                             如果他們還沒有被初始化,而initialize方法被呼叫

                             他們的Updated方法被呼叫

          Sys.Application物件的endCreatComponent方法被呼叫

               剩餘元件的endUpdate方法被呼叫

開發時Update狀態的使用方式

  1. 呼叫beginUpdate方法
  2. 修改屬性
  3. 呼叫endUpdate方法,此外,經常重寫Updated方法,提交元件更新資訊
一個改進版的Timer示例

建立一個名為BetterTimer.js的檔案

/// <reference name="MicrosoftAjax.js"/>
Type.registerNamespace("Demo");

Demo.Timer = function() {
    Demo.Timer.initializeBase(this);
    this._interval = 1000;
    this._enabled = false;
    this._timer = null;
}

Demo.Timer.prototype =
{
    add_tick: function(handler) {
        this.get_events().addHandler("tick", handler);
    },
    remove_tick: function(handler) {
        this.get_events().removeHandler("tick", handler);
    },
    _timerCallback: function() {
        var handler = this.get_events().getHandler("tick");
        if (handler) {
            handler(this, Sys.EventArgs.Empty);
        }
    },
    _startTimer: function() {
        this._timer = window.setInterval(
          Function.createDelegate(this, this._timerCallback),
          this._interval);
    },
    _stopTimer: function() {
        window.clearInterval(this._timer);
        this._timer = null;
    },
    get_interval: function() {
        return this._interval;
    },
    set_interval: function(value) {
        if (this._interval != value) {
            this._interval = value;
            this.raisePropertyChanged("interval");
            if (!this.get_isUpdating() && this._timer != null) {//timer正在執行,而且元件不出於Update狀態
                this._stopTimer();
                this._startTimer();
            }
        }
    },
    updated: function() {//重寫父類的updated方法
        Demo.Timer.callBaseMethod(this, "updated");//呼叫父類的updated方法
        this._stopTimer();
        if (this._enabled) {
            this._startTimer();
        }
    },
    get_enabled: function() {
        return this._enabled;
    },
    set_enabled: function(value) {
        if (value != this._enabled) {
            this._enabled = value;
            this.raisePropertyChanged("enabled");

            if (!this.get_isUpdating()) {//不出於更新狀態
                if (value) {
                    this._startTimer();
                }
                else {
                    this._stopTimer();
                }
            }
        }
    },
    dispose: function() {
        this.set_enabled(false);
        this._stopTimer();
        Demo.Timer.callBaseMethod(this, "dispose");
    }
}

Demo.Timer.registerClass("Demo.Timer", Sys.Component);

建立一個aspx的頁面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="BetterTimer.aspx.cs" Inherits="Demo12_BetterTimer" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Demo12/BetterTimer.js" />
            </Scripts>
        </asp:ScriptManager>
        
        Interval:<input type="text" value="1000" id="txtInterval" /><br />
        <input type="checkbox" id="chkEnabled" checked="checked" />
        <label for="chkEnabled">Enabled</label><br />
        <input type="button" value="Change" onclick="changeStatus()" />
        <hr />
        <div id="display"></div>
        <script language="javascript" type="text/javascript">
            Sys.Application.add_init(function() {
                $create(Demo.Timer,
                    { "id": "timer", "enabled": true },
                    { "tick": onTick });
                });
            window.counter = 0;
            function onTick() {
                $get("display").innerHTML = window.counter++;
            }

            function changeStatus() {
                var timer = $find("timer");
                timer.beginUpdate();
                timer.set_interval(parseInt($get("txtInterval").value, 10));
                timer.set_enabled($get("chkEnabled").checked);
                timer.endUpdate();
            }
            
        </script>
    </form>
</body>
</html>

Control模型(視覺化元件模型)

  • 封裝一個DOM元素
  • 提供統一的開發模型
  • 可以用於開發複雜元件
  • 建構函式接受一個element引數,表示這個元件封裝的DOM元素

Sys.UI.Control類成員

  • element只讀屬性//要封裝的元素
  • visibilityMode屬性//列舉:hide(0)相當於style.visibility,collapse(1)相當於style.display
  • visable屬性//bool,可見性
  • addCssClass方法//新增一個class樣式
  • removeCssClass方法//去除一個class樣式
  • toggleCssClass方法//如果沒有一個class樣式,而新增,如果有則去除
  • parent屬性//
  • onBubbleEvent方法
  • raiseButtleEvent方法
一個使用Control模型的示例

建立一個名為TextBox.js的檔案

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("Demo");
Demo.TextBox = function(element) {//注意這裡有一個引數,是要封裝的DOM元素
    Demo.TextBox.initializeBase(this, [element]);
    this._originalText = null;//儲存元素中原先有的值
}

Demo.TextBox.prototype =
{
    initialize: function() {
        Demo.TextBox.callBaseMethod(this, "initialize"); //呼叫父類方法
        //響應文字框的change事件
        $addHandler(this.get_element(), "change",
            Function.createDelegate(this, this._onTextChange));
    },
    _onTextChange: function(e) {
        if (this.get_isUpdating()) {
            return;
        }
        var handler = this.get_events().getHandler("textChange");
        if (handler) {
            var args = new Sys.CancelEventArgs();
            handler(this, args);

            if (args.get_cancel()) {
                this.beginUpdate(); //這樣做,是為了設定文字框的text改變的時候,又會觸發一個textChange事件
                this.set_text(this._originalText);
                this.endUpdate();
            }
        }
        this._originalText = this.get_element().value;
    },
    add_textChange: function(handler) {
        this.get_events().addHandler("textChange", handler);
    },
    remove_textChange: function(handler) {
        this.get_events().removeHandler("textChange", handler);
    },

    //一個text屬性
    get_text: function() {
        return this.get_element().value; //get_element方法,返回封裝的DOM元素
    },
    set_text: function(value) {
        if (value != null) {
            this.get_element().value = value;
        }
        else {
            this.get_element().value = "";
        }
    }
}

Demo.TextBox.registerClass("Demo.TextBox", Sys.UI.Control);

建立一個aspx頁面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TextBox.aspx.cs" Inherits="Demo12_TextBox" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Demo12/TextBox.js" />
            </Scripts>
        </asp:ScriptManager>
        
        <input type="text" id="textBox" />
        
        <script language="javascript" type="text/javascript">
            Sys.Application.add_init(function() {
            $create(Demo.TextBox,//建立的元件型別
                 null,//這裡不設定ID
                { "textChange": onTextChange },//響應事件
                null,
                $get("textBox"));//需要封裝的元素
            });

            function onTextChange(sender, args) {
                var message = String.format("The text would be changed to '{0}',are you sure?", sender.get_text());
                if (!confirm(message)) {
                    args.set_cancel(true);//確定“取消操作”
                }
            }
        </script>
    </form>
</body>
</html>

這樣,我們實現了文字在改變以後,提示使用者是不是確定操作,如果不確定這次操作,則可以撤銷這次操作,注意,textChange是在改變文字後,焦點離開文字框以後觸發的

我們使用這個元件,對一個普通的textbox進行的封裝,這就是一個Control模型的使用示例

$creat方法

  • 原型:$creat(type,properties,events,references,element);
  • referencts是一個字典,儲存物件屬性與其他物件的關係,key為屬性名,value為其他物件id
  • 保證initialize方法呼叫時,屬性已經被設定為所需要的物件,幾十呼叫$creat方法時,其他物件還沒有建立

複合控制元件

複合控制元件主要會涉及到Control模型中的以下兩個方法

  • raiseBubbleEvent(source,args);//由子控制元件呼叫,將觸發的事件向父控制元件傳遞
  • onBubbleEvent(source,args);//父控制元件重寫該方法,用於接受子控制元件向上傳遞過來的事件

這兩個方法的主要作用是降低父控制元件和子控制元件之間的耦合關係,例如子控制元件不需要知道它的父控制元件是誰,只需要呼叫這個方法,把觸發的事件向上傳遞就好啦,至於由誰來接受,這屬於另外一個控制元件的設計啦

一個複合控制元件的示例

建立一個名為ButtonList.js的檔案

/// <reference name="MicrosoftAjax.js"/>

Type.registerNamespace("Demo");

Demo.Button = function(element) {
    Demo.Button.initializeBase(this, [element]);

    this._context = null;//上下文物件,用於儲存控制元件的一些資訊
    this._onClickHandler = null;//click事件的監聽器
}

Demo.Button.prototype =
{
    initialize: function() {
        Demo.Button.callBaseMethod(this, "initialize");//呼叫基類方法
        this._onClickHandler = Function.createDelegate(this, this._onClick);
        $addHandler(this.get_element(), "click", this._onClickHandler);
    },
    //釋放資源,防止資源洩漏
    dispose: function() {
        this._onClickHandler = null;
        $clearHandlers(this.get_element());
        Demo.Button.callBaseMethod(this, "dispose");
    },
    //元素點選後會呼叫
    _onClick: function(e) {
        var eventArgs = new Demo.ButtonClickEventArgs(this._context);
        this.raiseClick(eventArgs);
    },
    
    //context屬性
    get_context: function() {
        return this._context;
    },
    set_context: function(value) {
        this._context = value;
    },
    //把事件向上傳遞
    raiseClick: function(args) {
        this.raiseBubbleEvent(this, args);
    }
}

Demo.Button.registerClass("Demo.Button", Sys.UI.Control);//注意,繼承自Sys.UI.Control


//自定義的EventArgs
Demo.ButtonClickEventArgs = function(context) {
    Demo.ButtonClickEventArgs.initializeBase(this);
    this._context = context;
}
Demo.ButtonClickEventArgs.prototype =
{
    get_context: function() {
        return this._context;
    }
}

Demo.ButtonClickEventArgs.registerClass("Demo.ButtonClickEventArgs", Sys.EventArgs);//繼承自Sys.EventArgs



Demo.ButtonList = function(element) {
    Demo.ButtonList.initializeBase(this, [element]);
    this._itemDataList = null;//儲存元件內包含的元件物件
}

Demo.ButtonList.prototype =
{
    initialize: function() {
        Demo.ButtonList.callBaseMethod(this, "initialize");
        for (var i = 0; i < this._itemDataList.length; i++) {
            this._createNewButton(this._itemDataList[i]);
        }
    },
    _createNewButton: function(data) {
        var buttonElement = document.createElement("input");//建立一個Input元素
        buttonElement.type = "button";//元素的type為button(按鈕)
        buttonElement.value = data.text;//按鈕上的文字
        this.get_element().appendChild(buttonElement);//按鈕新增到這個元件上
        
        //把建立的元素,用上面定義的元件進行封裝
        $create(Demo.Button,
            { "context": data.context, "parent": this },//指定父控制元件為自身(ButtonList)
            null, null, buttonElement);
    },
    get_itemDataList: function() {
        return this._itemDataList;
    },
    set_itemDataList: function(value) {
        this._itemDataList = value;
    },
    //當有事件被傳遞上來的時候呼叫
    onBubbleEvent: function(source, e) {
        this.raiseItemClick(e);
        return true;//表示事件已經被處理掉了,不需要向上傳遞
    },
    add_itemClick: function(handler) {
        this.get_events().addHandler("itemClick", handler);
    },
    remove_itemClick: function(handler) {
        this.get_events().removeHandler("itemClick", handler);
    },
    raiseItemClick: function(args) {
        var handler = this.get_events().getHandler("itemClick");
        //如果有這個事件
        if (handler) {
            handler(this, args);
        }
    }
}

Demo.ButtonList.registerClass("Demo.ButtonList", Sys.UI.Control);

然後,建立一個aspx頁面

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ButtonList.aspx.cs" Inherits="Demo12_ButtonList" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server">
            <Scripts>
                <asp:ScriptReference Path="~/Demo12/ButtonList.js" />
            </Scripts>
        </asp:ScriptManager>
        
        <div id="buttonList"></div>
        
        <script language="javascript" type="text/javascript">
            Sys.Application.add_init(
                function() {
                    var datalist =
                    [
                        {
                            text: "Item1",
                            context: "Item 1 has been clicked!"
                        },
                        {
                            text: "Item2",
                            context: "Item 2 has been clicked!"
                        },
                        {
                            text: "Item3",
                            context: "Item 3 has been clicked!"
                        }
                    ];
                    $create(Demo.ButtonList,//元件型別
                        { "itemDataList": datalist },//指定屬性
                        { "itemClick": onItemClick },//響應事件
                        null,
                        $get("buttonList"));//要封裝的元素
                }
            );

                function onItemClick(sender, args) {
                    alert(args.get_context());//得到args的context屬性
                }
        </script>
    </form>
</body>
</html>

執行頁面,我們點選按鈕就會看到彈出的結果,注意,這裡的click事件雖然是子控制元件(Button)發起的,但是最後處理它的是我們建立的複合控制元件(ButtonList),這就是我們的raiseBubbleEvent方法和onBubbleEvent方法的功能和它們的使用方法

Behavior模型

  • 另外一種視覺化元件模型,繼承與Sys.UI.Behavior
  • Control包裝DOM元素,Behavior為DOM元素提供功能
  • 一個DOM元素智慧由一個Control來包裝,但是可以使用多個Behavior進行裝飾

Behavior成員

  • 與Component元件相比唯一增加的屬性是name
  • 由於一個M元素上可以新增多個Behavior,因此如果要通過元素獲得Behavior物件就需要通過name屬性獲得
  • $get("elementId")["name_of_behavior"]