Widget Factory - Part 2
簡介
這篇談 widget 的生命週期 (lifecycle)。在繼承了 $.widget 之後,有幾個繼承而來的基本函式是一定要好好實作的,否則宣告的 widget 就很難有所作為,或者在使用時也會造成很多的不便和困擾。這篇談的就是這些非實作不可的基本函式;但並未真正實作出函式,只放了一些簡單的程式碼做為研究之用;而是先討論在整個 widget 的生命週期中,這些函式是在何時被呼叫、執行及真正要實作時,大概哪些作業要放在哪個函式中實作。
this.options
在談到必要的基本函式之前,一定要先提到一個很重要的屬性 - options,這個屬性的值是一個 Javascript 的物件,用來儲存這個 widget 在運作時所要使用到的屬性及屬性的預設值,還有當指定事件被觸發時,所要執行的回呼函式 (callback)。實際上,從 $.widget 中就繼承了一個 options 的物件,這個物件中只有 disabled 及 create 兩個屬性,在這個 widget 中所宣告和定義的 options,最終會和 $.widget 中所繼承過來的 options 合併 (merge) 成一個物件。若是繼承其它的模組,最終也會和該模組的 options 物件合併成一個物件。在整個 widget 的實作過程中,都以 this.options
來參考這個合併過後的物件。而下述的兩個函式: _setOptions() 及 _setOption() 也是針對 this.options 這個物件的屬性及屬性值在作業。
Basic Methods
widget 生命週期中一定會執行到的基本函式如下,這裡先簡單的介紹各函式的主要作用及執行時機,在範例的程式碼中,可以更清楚的看出各函式的執行時機。這些函式中,通常都會在函式的某處呼叫 this._super() 或 this._superApply() 以執行父物件中同名的函式,確保父物件中同名的函式也會被執行。
- _create()
- 這個函式只有在 widget 第一次被呼叫,將 widget 具體化 (instantiate) 時會執行。函式中首先要做的是在 DOM tree 中構建 widget 所需要的各個節點,及將要監聽的事件和該事件的處理器綁定 (bind) 在一起。因為只會被執行一次,所以在整個 widget 的生命週期中,只能做一次和只需做一次的作業,都放到這個函式裡。具體化時,執行完 _create() 之後,會緊接著執行 _init()。一般 _create() 可以不傳入參數,但也可以傳入一個 Javascript 的物件,物件的內容可以用來修改 this.oprions 的內容。
- _init()
- jquery-ui 中將建立 (create) 和初始化 (initialize) 視為兩個概念,因此分成兩個階段來執行。_init() 除了在第一次具體化時跟在 _create() 後面被執行一次之外,每次呼叫 widget 但不傳入任何參數時,例如:
$(selector).widget();
_init() 也都會被執行一次。此時,會先執行 _setOptions() 之後再執行 _init()。和 _create() 一樣, _init() 一般也不傳入參數,但也可以傳入一個 Javascript 的物件,物件的內容也是用來修改 this.oprions 的內容。注意:_init() 在整個 widget 的生命週期中,可能被執行不止一次,這點和 _create() 不同,因此,整個整個生命週期中只能執行一次的作業絕對不能放在這個函式裡。 - _setOption(key, value)
- 將 this.options 之名為 key 之屬性的值改成 value。這個函式實際上不會被外部程式直接呼叫,它都是透過 _setOptions() 函式的呼叫才會執行。如果某個 this.options 中的屬性值有限制條件,可以在這裡做檢查;或者屬性值變更後,需要觸發某事件等,都在這裡處理。重點是 _setOption() 會實際去改變 this.options 的屬性值,要等到屬性值改變完畢之後才能執行的作業,一定得在這個函式中確定 this.options 中的屬性值被修改完畢後才能進行。
- _setOptions(opts)
- 只要呼叫
$(selector).widget("option", key, value);
或是$(selector).widget("option", {key1:value1, key2:value2});
,以變更 this.options 中的屬性值,都會執行 _setOptions() 函式,再由 _setOptions() 為每一個傳入的屬性呼叫一次 _setOptions()。只有呼叫$(selector).widget("option", key);
以取得 key 屬性的值時,不會執行 _setOptions()。另外,就是在進行被始化時,會在執行 _init() 之前被執行。這個函式大多在進行改變 this.options 屬性值的前置作業。 - _destroy()
- 摧毀此 widget。把 destroy 譯成「摧毀」好像很可怕,但這個函式基本上,就是把 widget 在 DOM tree 中所建構的東西都清除乾淨,讓 DOM tree 回復到尚未建構這個 widget 之前的狀態。
$(selector).widget("destroy");
呼叫 destroy() 公用函式, destroy() 公用函式則把工作再指派給 _destroy()。所以可以不用理會 destroy() 公用函式 只要實作 _destroy() 函式即可。只是在有繼承父類別時,在清除 DOM tree 中的東西時,得特別注意一下清除的前後次序。如果在 widget 中一次把所有的東西都清光了,有時父類別的 _destroy() 函式會因為找不到東西清除而出現錯誤。
範例
範例在網頁下載完畢後,網頁上會出現一個橘紅色圓圈,這個圓圈其實跟 widget 的生命週期沒什麼關係。只是用來顯示網頁已經下載完畢而己。記得開啟「開發者工具」中的「主控台」看程式執行的結果。範例式程中,每個基本的函式中,都只寫了二行程式碼,第一行將該函式的名稱印在主控台上;第二行呼叫 this._super()
執行父物件中的相對應函式。主程式大略把一個 widget 在生命週期中會被外部程式呼叫的函式,都呼叫了一遍,從主控台中的輸出結果,可以看出每次呼叫時,哪些 widget 的基本函式會依照何種順序執行。
CSS
<style> html { width:100%; height:auto; } body { margin:0 auto; width:1200px; padding-top:20px; } #item { width:200px; height:200px; border-radius: 50%; background-color:coral; } </style>
CSS 中用 #item{} 顯示了一個用來表示網頁已經下載完畢的橘色圓圈。
HTML
<body> </body>
HTML 中只建立了一個 id="item" 的元件,用來顯示橘色圓圈和掛載 widget。
Script
<script> // widget created by widget factory (function($, undefined) { $.widget( "prod.item", { version:"1.0.0", options:{ product:"Coffee", price:80 }, _create:function(){ console.log("\t_create()"); this._super(); }, _init:function(){ console.log("\t_init()"); this._super(); }, _setOption:function(key, value){ console.log("\t_setOption()"); this._super(key, value); }, _setOptions:function(opts){ console.log("\t_setOptions()"); this._super(opts); }, _destroy:function(){ console.log("\t_destroy()"); this._super(); } }); })(jQuery); $(function(){ console.log('var elem = $("#item").item()'); var elem = $("#item").item(); console.log("elem.item()"); elem.item(); console.log('elem.item("option")'); console.log("\t%o", elem.item("option")); console.log('elem.item("option", {product:"Milk", price:50})'); elem.item("option", {product:"Milk", price:50}) console.log("\t%o", elem.item("option")); console.log('elem.item("option", "price", 60)'); elem.item("option", "price", 60); console.log('elem.item("option", "price")'); console.log("\t%s", elem.item("option", "price")); console.log('elem.item("destroy")'); elem.item("destroy"); }); </script>
第 5 行,習慣上會加上 version 屬性,並標示 widget 的版本號。
兩段程式都很簡單,一看就懂,不多做說明了。只是從主控台結果裡發現了一件事,要提醒一下:在第 36 行具體化物件時,不論是否有傳入物件參數以改變 this.options 的屬性值,都只會先執行 _create() 接著執行 _init(),是不會執行 _setOptions() 函式的,更不用說 _setOption() 函式了,因此,如果在這兩個函式中,有針對傳入參數的作檢核或是任何前置處理,都不會被執行到。如果在具體化 widget 時,有要傳入物件參數以改變 this.options 之內容的要求,則得在 _create() 函式中自行呼叫 _setOptions() 函式,以完成參數的檢核或前置處理作業。
沒有留言:
張貼留言