2016年4月28日 星期四

jQuery Widget Factory - part 3

Widget Factory - Part 3

簡介

這篇談 widget 有關事件觸發及事件處理的部份。首先,在 widget 進行具體化時,預設會觸發一個型態為 create 的事件,觸發的時機在執行完 _create() 之後,執行 _init() 之前。事件觸發後,widget 會先送一個事件給外部程式,然後去 this.options 物件裡找一個和事件型態名稱相同的屬性,以 create 這個事件為例,也就是屬性名為 create 的屬性,如果這個屬性的值正好是一個函式,這個函式就會被當成一個事件處理函式執行。如果找不到對應名稱的屬性,或對應名稱屬性的值不是函式,widget 就當這事件沒有必要處理,繼續往下執行。不只是預設觸發的 create 事件會這麼做,而是所有在 widget 內部觸發的事件,都會走完上述的這一趟程序。

當 widget 觸發一個事件,而將事件送到 widget 外部時,widget 會在訂定的事件型態名稱前面加上 widget 的名稱字串,然後將字串的字母都變成小寫。事件型態的名稱的字串中可以包含文、數字還有符號 "_" 及 "-"。例如: widget 的名稱為 "item",事件型態為 "create";此時,外部程式要監聽的事件名稱應為 "itemcreate"。

在 Widget Factory 中有三個跟觸發事件和處理事件相關的內建內部函式,這三個內部函式僅供呼叫使用,無需實作:

_trigger(type [, event ] [, data ])
觸發一個事件。
type
事件的型態名稱,賦予值為字串,字串中可以包含文、數字及 "_" 和 "-" 兩個符號。在這裡也可以用大寫字母,只是轉成事件型態名時,大寫字母都會被轉成小寫字母;為了避免麻煩,最好一律用小寫字母,反正還有兩個符號可以用來分隔字詞,還是看得清,讀得懂的。要記得在外部監聽 widget 的事件時,事件型態名稱前面還會加上 widget 的名稱,widget 的名稱中如果有大寫字母,也會被轉換成小寫字母。用中文取事件型態名稱也行,還真的可以成功運作,我有試過,感謝 UTF-8。
event
這是一個選項參數。如果是因為另一個事件的觸發才導致這個事件的觸發,可以將原事件的 event 參數,在此轉發出去給這個事件的處理函式,如果這是原始的觸發事件,就可以不給,但最好給個 null 值。
data
也是一個選項參數。賦予值是個 Javascript 的物件,可以透過這個物件傳送一些資料給事件處理函式。如果要給這個參數,前一個 event 參數就一定要給,至少要給 null 值。
_on([suppressDisabledCheck ] [, element ], handlers)
用來綁定事件和事件處理函式,綁定的事件可以由 widget 自行觸發,或是外部觸發。
suppressDisabledCheck
此為選項參數,賦予值為布林值,預設為 false。設定為 true 時,會先行檢查 element 是否處於 disabled 的狀態,如果不是,則執行事件處理函式,如果是,則不執行事件處理函式。
element
此為選項參數,賦予值為一個 jQuery 物件。不給時,預設為 this.element。給予時則指定派送事件給該物件。
handler
此為必要參數,參數一定得給;但使用的格式有點奇特。如果觸發事件的元件是 widget 的本身,則直接將事件型態名稱做為屬性名,屬性值為事件處理函式。倘若觸發事件的只是 widget 中的子元件,則屬性名的位置要放入一個字串,字串中以第一個空白字元做區分,第一個空白字元前為事件的型態名稱,第一個空白字元之後則全部都屬於 selector 的部份,語法和 CSS3 的選取器 (selector) 一樣,用來指定觸發事件的視覺元件,但選取的範圍侷限於 widget 的子輩元件。屬性值的部份依然是事件處理函式,這個要看範例程式會更清楚。
_off(element, eventName)
解除監聽事件。
element
為必要參數,指定要解除監聽的物件。賦予值為一個 jQuery 物件。
eventName
為必要參數,指定要解除監聽的事件型態。賦予值為一字串,字串中可包含多個事件型態的名稱,以空白字元分隔。

範例

範例中的視覺元件是兩個同心圓,點按內部的圓形,會改變內部圓形的顏色,點按外部的圓形,則不做任何反應。同心圓下有個按鈕,按下按鈕後,會解除內部圓形對 click 事件的監聽,之後再點按內部的圓形時,就不會有任何反應了。
記得開啟「開發者工具」中的「主控台」去查看輸出,可以對事件的處理時機有進一步的瞭解。

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:skyblue;
    text-align: center;
}
#itemCore {
    position: relative;
    top: 50%;
    left:50%;
    transform: translate(-50%, -50%);
    width:100px;
    height:100px;
    border-radius: 50%;
    background-color:lightgreen;  
}
</style>

CSS 中 #item{} 用來設定同心圓外部圓形的外觀, #itemCore{} 用來設定同心圓內部圓形的外觀,並讓內部圓形置於外部圓形的正中央。

HTML

<body>
    

</body>

HTML 中建立兩個 <div> 在流覧器中用圓形表示,還有一個按鈕。

Script

<script>
// widget created by widget factory
(function($, undefined) {
    $.widget( "prod.item", {
        version:"1.0.0",  
        options:{
            product:"Coffee",
            price:80,
            create:$.noop,  // default to no operation
            optionchanged:function(){
                console.log("\thandle optionchanged event in this.options");
            }
        },
        _create:function(){
            console.log("_create start");
            this._on({"click #itemCore":function(evt){
                console.log("\thandle a click event trigger by #itemCore");
                var elem = $("#itemCore")
                if(elem.hasClass("coral")){
                    elem.css("background-color", "lightgreen").removeClass("coral");
                } else {
                    elem.css("background-color", "coral").addClass("coral");
                }
            }});
            this._super();
            console.log("_create end");
        },
        _init:function(){
            console.log("_init start");
            this._super();
            console.log("_init end");
        },
        _setOption:function(key, value){
            this._super(key, value);
            console.log("_setOption");
            this._trigger("optionchanged", null, {key:key, value:value});
        },
        _setOptions:function(opts){
            this._super(opts);
        },
        _destroy:function(){
            this._super();
        },
        removeHandler:function(ent){
            this._off(this.element, "click");
        }
    });
})(jQuery);

  
$(function(){
    var elem = $("#item");
    // here use "item" + "create" as event type
    elem.bind("itemcreate", function(evt){
        console.log("\thandle create event outside of widget");
    });

    // use "item" + "optionchanged" as event type 
    elem.bind("itemoptionchanged", function(evt, data){
        console.log("\thandle optionchanged event outside of widget");
        console.log("\t%o", evt);
        console.log("\t%o", data);
    });

    $("#off").button().click(function(){
        $("#item").item("removeHandler");
    })
    // override create attribute in this.options
    elem.item({
        create:function(){
            console.log("\thandle create event in this.options");
        }
    });

    // trigger an "optionchanged"/"itemoptionchange" event
    elem.item("option", "price", 2.99);
});
</script>

第 6 行,指定 widget 的版本號碼,以便將來維護。
第 9 行,先定一個 create 屬性,預設值為 $.noop ,就是 no operation,不做任何事的意思。由 widget 在具體化時,由參數傳入來改寫參數值。
第 11~12 行,定義 optionchanged 事件的處理函式。
第 16~24 行,綁定由 #itemCore 所觸發的 click 事件,及事件的處理函式,注意第 16 行中,參數的寫法。
第 36 行,在呼叫 _setOption() 時觸發一個事件型態為 optionschanged 的事件,並變更後的屬性名及屬性值以 Javascript 物件的方式,當成參數傳出去。
第 44~47 行,定義一個名為 removeHandler 的外部函式,在函式中呼叫 this._off() 解除對 #itemCore 元件所觸發之 click 事件的監聽。
第 54~56 行,在 widget 外部綁定監聽 itemcreate 事件及其處理函式。
第 59~63 行,在 widget 外部綁定綁定監聽 itemoptionchanged 事件及其處理函式。
第 65~67 行,定義一個 jquery-ui 的按鈕,在觸發 click 事件時,於事件處理函式中呼叫 widget 中的 removeHandler 外部公用函式。,br> 第 69-73 行,具體化名為 item 的 widget,同時傳入一物件做為參數,物件中是對 create 事件的事件處理函式。
第 76 行,呼叫 widget 的 option 函式以觸發一個 optionchanged 的事件。

2016年4月27日 星期三

jQuery Widget Factory - Part 2

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() 函式,以完成參數的檢核或前置處理作業。

2016年4月26日 星期二

jQuery Widget Factory - Part 1

Widget Factory - Part 1

簡介

在 jQuery UI 中要撰寫一個視覺元件 (widget) 的模組, 在 jQuery UI v1.8 之前只能使用 plugin,自 jQuery UI v1.8 版之後則可以使用 Widget Factory 模組。該模組所提供的 APIs 在後繼的 v1.9, v1.10, v1.11 版都有少許的變更。這系列文章採用的是 v1.11 版的 APIs,這是寫文章時可供下載最新、最穩定的版本。使用 Widget Factory 時會需要用到兩個模組:core.js 和 widget.js;這兩個模組都存在於 jquery-ui.js 套件之下。所以必須載入 jquery-ui.js 或 jquery-ui.min.js。如果不會用到其它 jQuery 的介面元件,也不想載入整個 jquery-ui 的套件,就得將這兩個模組從 jquery-ui 套件中分離出來後,單獨載入。

這篇文章只解釋 Widget Factory 最重要的基礎部份,也就是只宣告一個 widget,完全沒有實作任何東西時,所宣告的 widget 會長成什麼樣。

使用 Widget Factory 宣告一個 widget 時所用的語法如下:

$.widget(name, [base], prototype);

name
字串值。為此 widget 所取的名字,名字必須包含命名空間,例如: myNameSpace.myWidget。命名空間只能有一層,撰寫應用程式時,基本上是用不到這個命名空間的,但一定得給;因為 jquery-ui 會在 $ (這個 "$" 是 jQuery 在 script 中的簡寫)之中建立一個名為 myNameSapce 的物件,而在 $.myNameSpace 之下,又有個名為 myWidget 的物件,這就是宣告後 widget 所在的位置。所有自訂的 widget,只要命名空間相同,都會被放在一起;這樣比較好管理。同一個名命空間下的 widget 名稱就不能重複了。
base
選項參數。此為所宣告之 widget 要繼承之模組名稱。例如: $.ui.button 或 $.ui.dialog。如果此參數從缺時,預設為繼承 $.Widget(注意:這裡 Widget 的 W 是大寫字母,表示它是一個物件類別的名稱)模組。
prototype
賦予值為一函式 (function),在此函式中必須實作所有的繼承自 $.widget 的函式,這些函式包含供內部使用的函式 (private method),及供外部呼叫的函式 (public method)。供內部使用的函式,依慣例,在函式名稱都前置一個底線 "_",如: _create();供外部呼叫的函式則無前置底線,如:enable()。這種命名方式並非強制的規則,只是一般通用的慣例。

this.options & this.element

當執行完 $.widget() 之後,widget 的基本架構就構建完成了。此時,widget 己具有兩個很重要的屬性:options 及 element。在實作 widget 的各函式時,常會使用 this.options 及 this.element 去參考這兩個屬性。

options
是個 Javascript 的物件,起始時預設內含兩個屬性:disabled 和 create。將來 widget 中要包含其它的屬性時,也都會放到這個物件裡。
disabled
預設值為 false。當呼叫 .disable() 時,此屬性值會被改成 true。程式中可以偵測此屬性值以決定 widget 的狀態。
create
預設值為 null。傳入值為一回呼函式 (callback function),當 widget 具體化 (instantiate) 時,會觸發一個型態為 create 的事件,同時將此回呼函式當成事件的處理函式執行。
element
目前使用到的 jQuery 物件。在 jQuery 中,函式常常依序針對數個 jQuery 物件逐一處理,而 this.element 之中只含了一個 jQuery 的物件,也就是當前正在處理的物件。

Public Methods

假如宣告了一個名為 myWidget 的 widget。要呼叫 myWidget 中之函式的方式為 myWidget(methodName),methodName 是函式名稱的字串。這種方式是傳統 jQuery 呼叫函式的方式。在 v1.11 版後,還可以用 myWidget.("instance").methodName() 的方式來呼叫。以下是定義完 widget 之後,在還沒有實作任何函式時,就可以呼叫而不會抛出錯誤訊息的函式,因為完全沒實作,所以也做不了什麼事。各函式細節的部份,等實作時再做說明。

myWidget.("option", optionName)
myWidget.("option", optionName, value)
myWidget.("option")
myWidget.("option", object)
第一個呼叫方式讀取 optionName 的值,optionName 需為字串值。第二個呼叫方式設定 optionName 的值為 value。第三個呼叫方式讀取所有 option 的值,傳回值為一個物件,內含所有的 option 及其現值。第四個呼叫方式一次設定數個 option 的值,傳入值為一個物件,物件中所含有的 option,其值都會被重新設定為物件中的值。
myWidget("widget")
傳回 jQuery 物件。這個函式不需要實作,就這樣用,其實這個函式只是把 this.element 傳回來而已。
myWidget("instance")
傳回 widget 的 instance。這是唯一不能用 myWidget("instance").instance() 方式來呼叫的函式,這樣呼叫也沒什麼意義。這個函式也不需要實作,它主要的作用就在提供另一種呼叫 widget 中其它公用函式的方法罷了。
myWidget("disable")
Disable widget。此時會在 widget 的屬性 class 中加入 nameSpace-widgetName-disabled 這個字串。字串中的 nameSpace 及 widgetName 由 widget 的 nameSpace 及 widgetName 的字串值來取代。然後把 options.disabled 的值改成 true。
myWidget("enable")
Enable widget。將 widget 之 class 中的 nameSpace-widgetName-disabled 字串刪除;然後把 options.disabled 的值改成 false。
myWidget("destroy")
清除 widget。

範例

範例中宣告了一個名為 prod.item 的 widget。並呼叫上述的各函式,大多數的結果都要到「開發者工具中」的主控台去看。只有 enable() 及 disable() 這兩個函式定義了 CSS 的類別,可以展示一點視覺效果。網頁下載後,過 2 秒會 disable widget,再過 2 秒會 enable widget,再過 3 秒會 destroy widget,因為只宣告了 widget 也沒實作,也沒什麼可以 destroy 的。

head





在 <head> 中記得要載入 jquery-ui.min.js。

CSS

<style>
html {
    width: 100%;
    height: auto;
}
body {
    margin: 0 auto;
    width: 1000px;
    padding: 20px;
}
#item {
    width: 200px;
    height: 200px;
    border: 2px solid gray;
    background-color: skyblue;
}
.prod-item-disabled {
    opacity:0.3;
}
</style>

CSS 中用 #item{} 定義了元件的基本外觀。
.prod-item-disabled{} 定義了當元件被 disabled 時,將元件的 opacity 值降到 0.3。這樣在執行時會造成一點視覺效果。

HTML

<body>
    
</body>

HTML 中定義了一個 id = "item" 的元件,作為 widget 的掛載點。

Script

<script>
// widget created by widget factory, should put into another file
(function($, undefined) {
    $.widget( "prod.item", {
  
    });
})(jQuery);

 
$(function(){
    console.log("jQuery version is " + $().jquery);
    console.log("jQuery-ui version is " + $.ui.version);

    console.log($.prod);

    var elem = $("#item").item();
 
    console.log(elem.item("option"));
    console.log(elem.item("widget"));
    console.log(elem.item("instance"));

    setTimeout(function(){
        elem.item("disable");
        console.log(elem.item("option", "disabled")); // true
    }, 2000);
    setTimeout(function(){
        elem.item("enable");
        console.log(elem.item("option", "disabled")); // false
    }, 4000);
    setTimeout(function(){
        elem.item("destroy");
    }, 6000); 
});
</script>

第 3~7 行,定義了一名為 prod.item 的 widget。一般來說,這一部份的程式碼在實務上實作時,都會分開放在另一個檔案之中,以便維護。
第 11~12 行,在主控台中列印出使用的 jQuery 及 jQuery-ui 的版本。
第 14 行,在主控台中列印 $.prod 物件,所有以 prod 做為命名空間的 widget 都會放在這個物件之中。
第 18~20 行,依序呼叫三個函式,並在主控台印出傳回值。 可以比較一下 widget() 和 instance() 兩個函式的傳回值有何不同。
第 22~25 行,設定一個定時器,在網頁下載 2 秒過後,disable widget ,並將 options.disabled 的值印在主控台中。
第 26~29 行,設定一個定時器,在網頁下載 4 秒過後,enable widget ,並將 options.disabled 的值印在主控台中。
第 30~32 行,設定一個定時器,在網頁下載 6 秒過後,清除 widget。這個只是呼叫了 destroy() 這個函式,完全沒做任何事。

2016年4月23日 星期六

dojo/fx

dojo/fx

簡介

dojo/fx 是架構於 dojo/_base/fx 之上的另一個動畫模組,擴充了 dojo 在動畫上的另一些功能。 這些額外的動畫函式基本上都是由 dojo/_base/fx 中的 animateProperty() 函式所實作,只是另外提供一個常用動畫函式的簡式介面罷了,所需傳入的參數和 animateProperty() 一樣,如果不熟悉傳入參數的格式,可以看 dojo/_base/fx 這篇。dojo/fx 模組所提供的動畫函式有:

wipeIn()
將元件由上至下慢慢顯示,顯示前不佔螢幕位置,顯示後佔螢幕位置。
wipeOut()
將元件由下至上慢慢消失,消失後不佔螢幕位置。
slideTo()
將元件滑動至指定位置。
chain()
將數個動畫串接,依序執行;串接的動畫可以作用在不同的元件上。
combine()
將數個動畫組合,同時執行;組合的動畫可以作用在不同的元件上。

wipeIn() & wipeOut()

先介紹 wipeIn() 和 wipeOut() 兩個函式。wipeIn() 和 wipeOut() 這兩個動畫函式,其功能和使用條件及使用方式都有相當的限制。個人覺得,如果需要類似的動畫效果,實在不如自己用 animateProperty() 寫一個,用起來也許會方便一些。首先,這兩個函式基本上沒有辦法處理元件的 padding 屬性,如果元件中有定 padding 屬性的值,在動畫時顯示的效果就很奇怪。使用 wipeIn() 時,會突然出現上下 padding 總和的高度,然後再慢慢出現其它的部份,還會超出上下 padding 的高度總合,最後才回縮到正常的高度。使用 wipeOut() 時,會先從元件的下方伸長上下 padding 高度的總和,然後再開始慢慢的消失。總歸一句,如果要用 wipeIn() 和 wipeOut() 這兩個動畫函式的元件就不要定 padding 的屬性。如果用在沒有文字的影象圖片上,也不要用 padding 設定,這兩個函式就還可以用。

其次,wipeIn() 函式的要求和限制更大,它不能指定出現後的高度,元件內容佔據多大的高度,元件最終就只能有這個高度,沒得商量。但在範例中,所看到 在執行完 wipeIn() 後,方塊的高度卻沒有因為內容而縮小,這是因為在 CSS 類別中定義了 display:flex;fles:none; 的原故,如果隔隣的方塊還在,所有的方塊便會維持一樣的高度,如果隔隣的方塊不在,就成了內容的高度,這是意外的發現。很神奇,不是嗎?不過再神奇,也救不了它不支援 padding 設定的事實。再其次,要使用 wipeIn() 函式的元件,得使用 display:none; 的設定,讓它一開始時不會出現在螢幕上,這個設定一定要設在元件的樣式裡,不能設定在元件的 CSS 類別中,不然該元件永遠不會出現在螢幕上。這是因為 wipeIn() 會去修改元件的 display 屬性值,將 display 的設定取消,好讓它顯示在螢幕上,如果 display:none; 是設在 CSS 類別中,WipeIn() 函式就改不到,而元件也永遠不會有露臉的機會了。

wipeIn() 及 wipeOut() 都是透過改變元件的 height 屬性值來逹到動畫的效果。在呼叫完這兩個函式後,元件的 height 屬性值,不論原先設定為何,在函式執行完畢後, height 的值都會被設定為 auto。如果後續還要對該元件做動畫處理,這點得特別注意。

slideTo()

這個功能可以移動元件在螢幕上的位置,但位置坐標是以視野 (viewport) 的絕對位置來設定。不論先前的設定為何,在呼叫完 slideTo() 之後,display 的值都會被改成 absolute。同時 top 和 left 的值都會設成移動過後的坐標。原先元件所在的位置會空出來。

範例

範例中建立三個按鈕及三個 <div>,一個用來展示 wipeIn(),一個用來展示 wipeOut()。二個 <div>都沒有設定 padding 屬性。下方的一個用來展示 slideTo(),這個沒有設定 margin,比較容易看到移動到逹的位置,在按下 Slide To 按鈕後,下方的方塊會移到流覽器的左角,top=0 及left=0 的地方。同時,最下方的水平分隔線會上移,因為此時方塊的元件樣式被改成 style="display:absolute; top=0; left:0"。 還有三條水平分隔線,用來觀察元件顯示前及消失前後,是否佔據螢幕空間。下圖為執行時的起始狀能。

CSS

<style>
html {
    width:100%;
    height:100%;
}
body {
    margin: 0 auto;
    width:1000px;
    height:100%;
    padding-top:20px;
}
.boxContainer {
    display:flex;
}
.box {
    margin: 10px;
    width:100px;
    height:100px; 
}
.out {
    flex:none;
    background-color: skyblue;
}
.in {
    flex:none;
    background-color: lightgreen;
}
.slide {
    margin:0px;
    flex:none;
    background-color: coral;
}
</style>

.out{} 及 .in{} 分別設定兩個 <div> 的外觀屬性,不設 padding 值。.slide{} 設定第三個 <div> 的外觀屬性,margin 值設定為 0。flex 值都設定為 none,以保持方塊的高度和寬度。

HTML

<body class="claro">
    
    
    
    
Here is some text

I can slide!

</body>

注意第 8 行,將 display:none; 設在元素的樣式裡。

Script

<script>
require([
    "dojo/parser",
    "dojo/ready",
    "dojo/dom-style",
    "dojo/fx",
    "dojo/on",
    "dijit/registry"
    ], 
function(
    parser,
    ready,
    style,
    fx,
    on,
    registry
){
    parser.parse();
    ready(1, function(){
        // both wipeIn() and wipeOut() can not handle padding attribute
        // After wipeOut(), element's attributes value - height:"auto"
        registry.byId("wipeOut").on("click", function(){
            fx.wipeOut({
                node:"outBox",
                duration: 3000
            }).play();
        });
        // After wipeIn(), element's attributes value - height:"auto"
        // to hide the element before calling wipeIn(), the display:none must be set in the
        // element's style, setting in the CSS class will cause element not display
        registry.byId("wipeIn").on("click", function(){
            fx.wipeIn({
                node:"inBox",
                duration:3000
            }).play();
        });
        registry.byId("slideTo").on("click", function(){
         fx.slideTo({
         node:"slideBox",
                top:0,
                left:0,
                unit:"px",
                duration:3000
            }).play();
        });
    });
});
</script>

程式就將三個按鈕設定在點按後呼叫對應的函式執行動畫,將動畫執行旳時間設定為 3 秒,其它也沒什麼需要解釋的了。

2016年4月16日 星期六

dojo/_base/fx

dojo/_base/fx

簡介

dojo/_base/fx 模組是 dojo 中所有動畫模組的基礎模組,而 animateProperty()則是其中的基礎函式。animateProperty() 函式傳回一個 dojo/base/fx::Animation 物件,呼叫這個傳回物件的 play() 函式,動畫才會正式起動。在實作時,以 animateProperty() 函式傳入參數設定動畫的起始及結束時的樣式狀態及動畫執行時的一些參數,例如:動畫開始前的延遲時間、動畫執行時的歷時長度、執行的次數。。。等等; 在取得傳回的 Amination 物件後,再決定何時開始動作及如何動作。

animateProperty()

animateProperty() 傳入的參數為一個 Javascript 物件,該物件中各屬性的意義如下:

node
要進行動畫的節點。可以直接傳入該元件的 id 名。
properties
賦予值為 Javascript 物件,陳述進行動畫之元件的起始樣式和結束樣式。每一個樣式屬性可以只賦予一個結束值,則該樣式屬性以該元件的現有值為起始值;或給予一個 Javascript 物件,物件中包含 start 及 end 兩個屬性,各代表起始值及結束值(見程式碼)。
delay
延遲時間,單位為 ms。動畫在收到 play() 指令後,會等候 delay 所設定的時間,之後才開始進行動畫。預設值為 0。
duration
整個動畫進行過程所需經歷的時間,單位為 ms。預設為 350ms。
rate
執行動畫時更新畫面的速度,預設值為 20。也就是 1000 ms / 20ms = 50,表示每秒鐘更新 50 次畫面。速度有點快,若改為 25,則為 1000ms / 25ms = 40; 也就是每秒鐘更新 40 次畫面。以此類推。
curve
賦予值為一個包含起始值和結束值的陣列,預設值為 null。目前找不到任何相關的資料或文件說明如何使用這個參數。
easing
賦予值為函式,預設值為 null。用來改變動畫執行時,在各時間點的加、減速。如要改變預設值,得自己寫函式。
repeat
動畫要重複執行幾次,預設值為 0。此時會執行一次,然後停留在結束的畫面。如果設定為 1,則會執行一次,但畫面會回到起始畫面。其餘設定以此類推,執行完次數後,都會回到起始畫面。
除了這些基本參數之外,尚有數個在不同動畫執行時間點的回呼函式可以指派:
beforeBegin(arg)
在動畫開始前呼叫。參數 arg 為一 DOM 元件,樣式屬性含有起始值。
onBegin(arg)
在動畫開始時呼叫。呼叫的時機及傳入的參數和 onPlay() 似乎都一樣,不知作用何在?根據原始碼的註解,作者好像也有這樣的疑問。參數 arg 為一物件,內含所有要改變之樣式屬性的起始值。
onPlay(arg)
動畫執行時呼叫一次,參數 arg 為一物件,內含所有要改變之樣式屬性的起始值。
onAnimate(arg)
動畫執行過程中,每次有改變樣式的屬性後都會呼叫一次。參數 arg 為一物件,內含所有要改變之樣式屬性改變後的值。
onPause(arg)
動畫暫停時呼叫。參數 arg 為一物件,內含所有要改變之樣式屬性的現值。
onStop(arg)
動畫中止時呼叫。參數 arg 為一物件,內含所有要改變之樣式屬性的現值。
onEnd(arg)
動畫結束時呼叫。參數 arg 為一 DOM 元件,樣式屬性含有結束值。

dojo/base/fx::Animation

傳回的 Animation 物件則具有以下函式:

status()
傳回動畫的目前狀態。可能值為 "active", "stopped", "paused" 的字串值。
play(delay, gotoStart)
開始執行動畫。如果先前的狀態為 "paused"(暫停),則由暫停處開始。如果先前狀態為 "stopped",則重新開始。delay 為延遲多少 ms 後開始, gotoStart 如果值為 true,則重新開始; 否則從目前的畫面開始。目前的測試狀況是:只要先前的狀態是 "stopped",一律會重新開始。 只有在先前的狀態是 "paused" 時會有差別。
stop(gotoEnd)
停止動畫的執行,進入 stopped 的狀態。gotoEnd 的值為 true 時,中止動畫執行並跳到動畫的結束畫面,預設為 false,動畫停留在中止的畫面。測試結果顯示, gotoEnd 的為 true 時,動畫會停止但不會進到結束的畫面,應該是 dojo 的 bug。
pause()
暫停動畫,動畫會停留在暫停時的畫面;此時如果再執行 play(),則依 play() 的 gotoStart 參數值的設定,重新開始動畫或由暫停時的畫面繼續動畫的執行。
gotoPercent(percent, play)
畫面直接跳到動畫執行到 percent(百分比)時的畫面,percent 的賦予值為浮點數值 0.0 至 1.0 之間。play 值為 true 時,動畫會繼續執行,值為 false 時,則停留在執行 percent 的畫面上。這裡的行為模式也有點出人意料。如果呼叫 gotoPercent() 時動畫正在執行,也就是狀態為 "active",而 play 值為 true 時,則動畫會立即跳至 percent 處,並繼續執行。如果 play 值為 false,則動畫會立即暫停,並停留在暫停的畫面處,直到下一次呼叫 play() 時,畫面直接跳到 percent 處並開始執行直到動畫結束。如果呼叫 gotoPercent() 時,動畫並未開始執行,即使 play 設定為 true,呼叫 gotoPercent() 也不會開始執行動畫,要等到呼叫 play() 時,才會直接跳到 percent 處並開始動畫。

範例

範例中建立四個按鈕及一個用來展示動畫的方塊,動畫執行結束時,方塊面積會變大,同時顏色會由淺綠變成深綠。四個按鈕各標示了點按後動畫會採取的動作,可以用不同的組合來查看動畫的行為。

CSS

<style>
html {
    width:100%;
    height:100%;
}
body {
    margin: 0 auto;
    width:1000px;
    height:auto;
    padding-top:20px;
}
.box {
    width:200px;
    height:200px;
    padding:8px;
    background-color:lightgreen;
}
</style>

CSS 中設定方塊的起始值。

HTML

<body class="claro">
    
    
    
     
    

</body>

HTML 中建立四個按鈕及一個展示動畫用的方塊。二條水平分隔線,用以顯示方塊是否佔據螢幕的位置。

Script


程式並不複雜,可以一看就懂,不多做解釋了。註解的地方可以取消註解,看看回呼函式何時被呼叫,及傳入的參數為何。

2016年4月12日 星期二

dojo/query (part 4) NodeList-fx

dojo/query (part 4) NodeList-fx

簡介

dojo/NodeList-fx 為 dojo/query 擴充了動畫的功能。原先 dojo 中的動畫功能分拆在 dojo/_base/fx, dojo/fx, 及 dojox/fx 三個模組裡;在 dojo/NodeList-fx 擴充模組裡則含括了大部份的功能。雖然說 dojo/query 及一系列的擴充模組有點仿效 jQuery 的用法,但因為基礎架構上的差異及呼叫函式方法的不同,在使用函式的方法上和參數的傳遞上還是有些差異的。所有 dojo/NodeList-fx 的動畫函式在呼叫後,都會回傳一個 Animation 物件,必需再鏈接 play() 函式,才會執行動畫。例如:query(".box").fadeIn().play();在執行完 play() 之後傳回的物件就不是 Animation 物件了,因此不能再鏈接動畫函式了。大多數的函式在執行時的行為都還滿容易理解,也很符合預期的情況,其中只有 wipeIn() 函式的行為頗為令人費解,和 jQuery 的 wipeIn() 行為大相逕庭。詳細研究後,在範例中另用一個方塊來展示其功能。原因是 wipeIn() 函式針對 element 的樣式 (style) 做變更,似乎又為了配合某些流覽器的行為,對 padding 屬性有特別的處理,最後使得 wipeIn(properties) 函式只能用特殊的寫法來逹到某種效果。詳細情況請參照範例程式中的寫法。

以下列出各動畫函式

fadeOut(properties)
淡出。針對透明度 (opacity) 屬性做改變,動畫結束後,opacity 屬性值為 0,該元件依舊佔據螢幕上的位置。參數 properties 是個物件,指定動畫執行時的一些條件,其物件屬性如下列所示:
delay
設定動畫延遲多久後開始執行,單位為 ms。
duration
設定動畫執行經過所需的時間,單位為 ms。
rate
設定動畫畫面的間隔時間,單位為千分之一秒 (ms),預設值為 20ms,也就是 50 frames/sec。這個預設值有點高,快得讓人看不清變化,可以設低一點。將 rate 設為 25 或 30,也就是每 25ms 或 30ms 顯示一次畫面,這樣比較看得清楚變化。
auto
設定為 true 時,表示後面要鏈接其它的動畫函式。
fadeIn(properties)
淡入。針對透明度 (opacity) 屬性做改變,動畫結束後,opacity 屬性值為 1。
wipeOut(properties)
逐漸消失。針對元件的樣式做變更,會改變 height 和 display 的值,動畫結束後,height=auto,display=none。因為 display=none,此時元件在螢幕上不佔位置。
wipeIn(properties)
逐漸顯現。元件樣式的 position 值需設定為 absolute、 display 值需先設定為 none,如果需要 padding 值,必須在內層多加一個元件,並在內層元件中設定 padding 值。動畫結束後,height=auto、display 為預設值。wipeIn() 結束後的高度沒有辦法指定,只能是內容加上 padding 的高度。個人真的不推薦使用 dojo 中的 wipeIn() 和 wipeOut() 兩個函式,太麻煩了。
slideTo(properties)
移到螢幕上指定的絕對位置。針對 top 及 left 屬性做改變,動畫結束後,position 的值為 "absolute", top 及 left 的值為指定值。
animateProperty(properties)
指定動畫開始時及結束時之各屬性的指定值,想要創造精彩炫目的動畫,就靠這個函式了。詳情請看程式的解說。

範例

範例的圖片只顯示了範例起始的狀態。操作時,先按 FadeOut 按鈕,等方塊淡出後,再按 FadeIn 按鈕,該方塊淡入。按完 WipeOut 按鈕後,方塊會消失,此時,水平分隔線仍在原地不動,顯示出消失的方塊實際上還佔著螢幕的位置。按下 WipeIn 按鈕,則會在水平線下方顯示另一個黃色的方塊。按下 SlideTo 按鈕,則方塊會移動,之後,會同時改變其顏色及大小;這時注意一下水平分隔線的位置,因為是移動到絕對位置,此時,水平分隔線的位置依然是相對的,所以就被移到上方了。

CSS

<style>
html {
    width:100%;
    height:100%;
}
body {
    margin:0 auto;
    width:1000px;
    height:100%;
    padding-top:20px;
}
.box {
    box-sizing: border-box;
    margin:20px;
    width:200px;
    height:200px;
    border: 2px solid black;
    padding:50px;
    background-color:blue;
}
</style>

CSS 中設定類別 .box 之各樣式屬性的初始值。

HTML

<body class="claro">
    
    
    
    
    
    
Demo box

</body>

HTML 中設定五個按鈕,分別觸動不同的動畫效果。並設定兩個方塊以展示動畫效果,其中 id="wipeInBox" 的方塊,,特別用來展示 wipeIn() 的效果。其中的 <hr> 水平分隔線用來顯示淡出的方塊依然會佔據螢幕位置。

Script


第 21~25 行綁定按鈕觸發事件時所要執行的函式。
第 29 行設定了在 2 秒後才開始執行動畫,因此按下按鈕後,要耐心等兩秒鐘,才會開始動作。
第 54~68 行是最精彩的部份,slideTo() 先將方塊移至 top=100px, left=300px 的位置,之後再用 animateProperty() 函式,經過 5 秒的時間,將背景顏色由紅色轉成綠色,在變換顏色的同時,將尺寸由 200px x 200px 縮小至 100px x 100px。
第 58 行設定各屬性若需要使用測量單位時,單位值為 "px" 這也是預設值。
第 62 行 backgroundColor 的設定值,指定了 start 和 end 兩個屬性值,因此,在動畫執行時,會先將方塊的顏色轉成紅色,最後才變成綠色。而第 63,64 行 width 和 height 屬性都只設定了一個值,此時,便以元件的現值為起始值,在此設定的值為結束值。 注意第 59 行的 auto 屬性須設定為 true,否則就不能自動鏈接後繼的動畫函式了。

2016年4月11日 星期一

dojo/query (part 3) NodeList-traverse

dojo/query (part 3) NodeList-traverse

簡介

在 DOM Tree 中搜尋元件時,並非每次都需要搜尋整份 HTML 文件;有時,會希望以搜尋到的某一元件做為參考點,由此,再依和參考點的對應關係去搜尋相關的元件。NodeList-traverse 這個模組便為 dojo/query 模組擴充了這方面的功能;讓搜尋可以依參考點的父輩、同輩,子輩等等各種關係,找到對應關係的元件。這種作業方式,在撰寫或處理略為複雜的介面元件模組時特別方便有用; 這樣也不需要傷神為每一個在介面中的小節點或子元件取一個用來識別的 id 屬性,或 CSS 類別,才能搜尋到該元件,只要找到介面元件中的參考元件,便能依對應關係找到介面中的其它元件。

以下列出部份的函式,用法如: query(Selector).children(filter); 其中 filter 參數為選用,可以給或不給,賦予值如同 Selector,也是使用 CSS3 選擇器的篩選條件式,用來將 qeruy 選取到的元件再做進一步的篩選。

children(filter)
取得所有選取元件的子輩元件。取得子輩時,只會取得下一層子輩。
parent(filter)
取得所有選取元件的父輩元件,取得父輩時,只會取得上一層的父輩。
parents(filter)
取得所有選取元件的父輩元件,這個函式會取得所有上層的父輩,一直到 <body> 及 <html>,通常這並非所要的結果,因此,這個函式一般都會給參數 filter,以篩選取得父輩的元件。
siblings(filter)
取得所有選取元件的同輩元件。
next(filter)
取得所有選取元件的下一個同輩元件,因為 next() 只會傳回一個元件,參數 filter 在這裡的作用,比較像指定條件,而非篩選。換句話說,這個函式會傳回同一輩中,下一個符合 filter 條件的元件。
nextAll(filter)
取得所有選取元件之後的所有同輩元件(不含選取元件本身),因為 nextAll() 通常會傳回不只一個元件,參數 filter 在這裡的作用又回到篩選。換句話說,這個函式會傳回在選取元件之後的同一輩中所有符合 filter 條件的元件。
prev(filter)
取得所有選取元件的上一個同輩元件,因為 prev() 只會傳回一個元件,參數 filter 在這裡的作用,比較像指定條件,而非篩選。換句話說,這個函式會傳回同一輩中,上一個符合 filter 條件的元件。
prevAll(filter)
取得所有選取元件之前的所有同輩元件(不含選取元件本身),因為 prevAll() 通常會傳回不只一個元件,參數 filter 在這裡的作用又回到篩選。換句話說,這個函式會傳回在選取元件之前的同一輩中所有符合 filter 條件的元件。
andSelf()
將參考元件本身加入 NodeList 之中,例如在使用 next(), nextAll(), prev() 及 prevAll() 等函式時,如果希望 NodeList 中包含參考點的元件,就必須在函式之後鏈接 .andSelf() 函式。使用時,必須注意 .andSelf() 在鏈接時的所在位置,不然取得的結果可能會出乎意料。

範例

在範例中構建一個簡單的組織圖,用來展現如何透過 NodeList-traverse 來尋訪元件。要找到一個特定的元件,可以有無數種的尋訪方式,範例中的程式並非唯一的答案。實作時,還是八仙過海,各顯神通吧!

CSS

<style>
html {
 width:100%;
 height:100%;
}
body {
 margin:0 auto;
 width:1000px;
 height:100%;
 padding-top:20px;
}
.company {
 width:400px;
 border: 2px solid gray;
 padding:30px;
}
.sales,.engineers {
 border: 2px solid gray;
 padding:30px;
 
}
.employee {
 border: 2px solid gray;
 padding:10px;
}
</style>

CSS 中設定各類別的屬性,讓各組織單位具有邊框,同時讓組織圖依組織架構內縮,這樣比較容易看出組織間的階層關係。

HTML

<body class="claro">
Company
Sales
John
Jason
Judy
Julian
Engineers
Susan
Sean
Sally
</body>

HTML 中用 <div> 定義出組織之間的階層關係。

Script


第 20 行先用 query(".sales")找到 sales 部門,用 children() 取得其所有子輩元件,用 at(0) 取得子輩元件的第一個元件,再用 next() 取得其後的一個元件,將其背景色改成淺綠。當然,用 at(1) 可以直接取得子輩元件中的第二個,不須多此一舉用到 next(),但這只是範例,就別太計較了。
變數 element 中,此時存放的是 Jason 這個元件,第 21 行取得其父輩元件,自然就是 class="sales" 的 <div> 了。 將其背景改成粉紅色。
第 23 行用 silblings() 取得 sales 的同輩,也就是 class="engineers" 的 <div>,將其背景改成淺藍色。
第 24 行用 children() 取得 engineers 的所有子輩元件,並將其背景改成淺灰色。

dojo/query (part 2) - NodeList-manipulate

dojo/query (part 2) - NodeList-manipulate

簡介

NodeList-* 這幾個模組從來不會單獨使用,都是附加在 dojo/query 這個模組之上,用以擴充 dojo/query 的函式,藉以針對 query 所搜尋到的元件做類似批次作業的變更。利用 NodeList-manipulate 擴充 dojo/query 後,dojo/query 就會具有 dojo/dom-* 一系列模組的功能,只是函式名稱及傳遞的參數略有不同,函式的參數中不需傳入要改變屬性的節點或節點名稱。這個模組基本上在摸擬 jQuery 的作業方式,所有的函式都可以鏈接 (chain up);其中最大的不同在於操作 DOM Tree 的方式,幾乎完全摸擬 jQuery 的作業,和 dojo/dom-construct 模組的作業有很大的差異。還有,在改變元件樣式時,jQuery 用的是 CSS() 函式,而 NodeList-manipulate 則保留使用 dojo 的 style() 函式。在監聽事件時,jQuery 用 bind() 函式,而 NodeList-manipulate 也保留了 dojo 使用的 on() 函式。操作 DOM Tree 的這一部份,去看 jQuery 的文章也許比看 dojo 的文章要來的詳盡。

底下並未列出所有的函式。

新增元件

append(element)
將 element 加在選取元件之中,成為選取元件的最後一個子元件,element 為一個元件,或一段 HTML 程式碼。
prepend(element)
將 element 加在選取元件之中,成為選取元件的第一個子元件,element 為一個元件,或一段 HTML 程式碼。
before(element)
將 element 加在選取元件之後,成為選取元件的同輩元件,element 為一個元件,或一段 HTML 程式碼。
after(element)
將 element 加在選取元件之前,成為選取元件的同輩元件,element 為一個元件,或一段 HTML 程式碼。

移動元件

appendTo(selector)
將所有選取元件依序加到 selector 之中,成為 selector 最後的子元件,selector 為選取元件的條件。
prependTo(element)
將所有選取元件依序加到 selector 之中,成為 selector 最先的子元件,selector 為選取元件的條件。
InsertBefore(selector)
將所有選取元件依序加到 selector 之前,成為 selector 最後的同輩元件,selector 為選取元件的條件。
InsertAfter(selector)
將所有選取元件依序加到 selector 之後,成為 selector 最後的同輩件,selector 為選取元件的條件。

其它

wrap(element)
將所有選取的元件,變成 element 的子元件。
replaceWith(element)
用 element 元件取代所有選取的元件。

範例

範例很簡單,就在清單中的每一個清單項目後面加入斜體的 "pie" 字,並用鏈接的函式改變清單字體的顏色。

CSS

<style>
html {
 width:100%;
 height:100%;
}
body {
 margin:0 auto;
 width:1000px;
 height:100%;
 padding-top:20px;
}
</style>

CSS 中沒有特別需要定義的項目。

HTML

<body class="claro">
 

Fruits

  • Apple
  • Banana
  • Cherry
</body>

HTML 中建立一份清單即可。

Script


第 8 行的 "dojo/NodeList-manipulate" 只是用來擴充 dojo/query 模組,而且又是在最後一個,所以在 function() 中的參數中,可以不用列名。但如果不是放在最後面,而其後有其它的參數時需要給予參數名時,就必須給予一個參數名,不然 dojo 會在載入模組和參數名稱之間的對應造成混亂而給出錯誤訊息。
第 19 行選取 CSS 類別為 .fruit 的所有清單項目。
第 20 行,加入 <em> Pie</em> 這段 HTML 程式碼到每一個清單項目的後面,並用鏈接函式 style() 改變字體的顏色。

2016年4月8日 星期五

dojo/query (part 1)

dojo/query (part 1)

簡介

在 dojo 中,要搜尋並鎖定 DOM Tree 中的元件 (element) ,除了透過 dojo/dom 模組的 dom.byId() 及透過 dijit/registry 模組的registry.byId() 之外,還有一個 dojo/query 模組可供使用。前二者不同的地方在於: dom.byId() 根據傳入的 id 屬性值,直接傳回一個 DOM 的元件,而 registry.byId() 則根據傳入的 id 屬性值,傳回一個 dijit (dojo widget) 物件 (object),還要進一步使用 registry.byId().domNode 才能真正取得 DOM 元件;即便如此,取得的元件和 dom.byId() 所傳回的元件稍有不同,可以用 console.log() 函式將兩者的輸出比較一下,就明白了。如果要變更或使用 DOM 元件,最好直接使用 dom.byId()。這兩者都只能使用 id 的屬性值作為搜尋的條件。

dom/query 模組在搜尋條件上,使用了 CSS3 的選擇器 (selector),因此,在搜選的條件上廣泛了許多。(事實上 dojo 有好幾個選擇器可供選用,但 CSS3 的選擇器己經足以應付大多數的需求,所以也不用費事再試著去設定和使用其它的選擇器了。) id、標記名 (tag)、 CSS 類別名 (class)、屬性值等等,都可以用來搜尋 DOM 中的元件。dom/query 的傳回值為 NodeList,其中包含所有符合搜尋條件的元件,NodeList 本質上就是個 javascript 的陣列 (Array),本身具有的函式不多,但可以透過加載下列的幾個模組擴充其功能:

dojo/query 這個模組中提供最重要的函式是 instantiate(),這個函式可以用程式法將所有選取到的元件轉變成 dojo 的 dijit。

dojo/NodeList-data
在元件中添加自訂屬性的擴充功能。這個擴充功能實際上用 dojo/dom-attr 模組就可以做到大部份的功能,個人猜想它應該是因為 HTML5 之前用 "data-" 前導的自訂屬性並非標準,為此而存在的。在加載了 NodeList-manipulate 模組之後,NodeList 也具有 attr() 函式,加載這個模組就顯得有點多餘了。
dojo/NodeList-dom
模擬 dojo/dom-* 的功能。
dojo/NodeList-html
變更元件內容的擴充功能。
dojo/NodeList-manipulate
這個擴充模組可以讓 dojo 像 jQuery 一樣對 DOM Tree 運作。個人認為這個擴充模組最好用了,只要加載了這個模組,就具有 dojo/NodeList-data、dojo/NodeList-dom 及 dojo/NodeList-html 三個模組的函式,可以針對符合選取條件的所有元件做批次作業。這樣就方便許多了。
dojo/NodeList-traverse
擴充尋訪 (traverse) 的功能,這個功能也很好用,語法也跟 jQuery 很類似。
dojo/NodeList-fx
添加動畫的功能。像是 anim(), fadeIn(), fadeOut(), wipeIn(), wipeOut(), slideTo() 等等。大約就是 dojo/_base/fx, dojo/fx, 及 dojox/fx 三個模組的集合版。

範例

範例中展示如何使用 instantiate() 函式,將一個清單中,CSS 類別為 .button 的三個項目都轉變成按鈕。

CSS

<style>
html {
    width:100%;
    height:100%;
}
body {
    margin:0 auto;
    width:1000px;
    height:100%;
    padding-top:20px;
}
</style>

HTML

<body class="claro">
    

Fruit Buttons

  • Apple
  • Banana
  • Cherry
</body>

在 HTML 中建置一個具有三個項目的無序號清單。

Script


第 20~25 行使用 instantiate() 函式將清單項目轉化成 dojo 的按鈕 (Button)。
第 26 行取得第二個按鈕的 DOM 元件。

2016年4月6日 星期三

dijit/layout/BorderContainer

dijit/layout/BorderContainer

簡介

如果想設計一款應用的介面外觀和一般全螢幕的桌面應用程式相似時,則 dijit/layoutBorderContainer 是一個相當稱手的佈局方式。基本上,這種佈局方式將螢幕區分成「上」、「下」、「左」、「右」、「中」五塊區域;只有「中」這塊區域是必要的,其它都可有可無。在設定各塊區域的尺寸時,只要設定「上」、「下」、「左」、「右」四塊區域,剩下的空間都會留給「中」這個區域。「中」這個區域只能有一個區塊,其它四個方向的區域中的區塊個數則可自訂。區塊必須使用 dijit/layout/ContentPane 或 dojox/layout/ContentPane,至於使用那一種,可根據需求決定。這些區塊都必須容納在一個 BorderContainer 中。

佈局的方式有兩個基本款式: headline 和 sidebar(見圖示),隨應用的需要再加以變化即可。當一個區域中有數個區塊時,layoutPriority 參數的數值會決定區塊的所在位置,數值愈大則愈接近「中」的區域。區塊和區塊間,或區域和區域間可以有間隔 (gutter),也可以不要有間隔。「上」、「下」、「左」、「右」四個區域中的區塊可以設定具有「把手」(handle) 用以動態調整區塊的大小;但因為「把手」的位置在於區塊和區塊的間隔中,所以只要定了「把手」,有「把手」的地方,區塊間就一定會有間隔。間隔的預設寬度為 5px,「把手」的顏色和間隔的底色相近,眼力不好的人,得注意看。

所有的參數分成兩部份,一部份定在 BorderContainer 的元件中,用來設定整體,一部份則定在各個 ContentPane 中,用來設定每個區塊。

BorderContainer

design
設定 BorderContainer 的基本款式,有 "headline" 及 "sidebar" 兩種,樣式請參看下列圖示。預設為 "headline"。

Headline

Sidebar

gutters
布林值,當 true 時,會在各區塊間生成間隔。預設為 true。gutter 預設的寬度為 5px,如果要變更寬度成 8px,必須修改
.claro .dijitSplitterV, .claro .dijitGutterV {
    width:8px;
}
.claro .dijitSplitterH, .claro .dijitGutterH {
    height:8px;
}

如果要修改「把手」的寬度和顏色,則修改 CSS 如下所示:
.claro .dijitSplitterV .dijitSplitterThumb {
    width:2px;
    background-color:red;
}
liveSplitter
當值為 true 時,拖曳「把手」以改變區塊大小時,區塊的尺寸會立即隨著拖曳的動作變更;當值為 false 時,區塊會一直等到拖曳停止時,才真正改變區塊的尺寸。改變一下範例中的設定值,實際操作一下會更容易理解。預設值為 true。

ContentPane

region
指定 ContentPane 在 BorderContainer 中的所在位置,每一個 ContentPane 都要給,允許值為 : "left", "right", "top", "bottom" 及 "center"。
layoutPriority
當同一個區域有一個以上的 ContentPane 時,用來指定 ContentPane 相對於中央區塊的位置,值為數值,數值愈大,愈靠近中央區塊。
splitter
當設定值為 true 時,產生一個用來改變區塊大小的「把手」。預設為 false。
minSize
當 splitter 設定值為 true 時,該區塊在改變尺寸時,所允許的最小尺寸。若區塊為「左」、「右」,指的是最小的寬度,若為「上」、「下」,指的是最小的高度。
maxSize
當 splitter 設定值為 true 時,該區塊在改變尺寸時,所允許的最小尺寸。若區塊為「左」、「右」,指的是最大的寬度,若為「上」、「下」,指的是最大的高度。

範例

範例中展示一個 headline 模式的佈局。

Headline

CSS

<style>
html {
    width:100%;
    height:100%;
}
body{
    margin:0 auto;
    width: 1000px;
    height:100%;
    padding:20px;
}
</style>

範例中的 CSS 中沒有什麼需要特別的設定。

HTML

<body class="claro">
    
Top 1
Top 2
Left
Center
Right
Bottom
</body>

範例中以程式法建立 BorderContainer,所 HTML 中只要建立各個掛載點即可。

Script


注意:在第 39~42 行中,對於位於中央的 ContentPane 在 style 的設定中,是完全沒有設定其高和寬的值;這是因為在 BorderContainer 的設計中,中央區塊的大小是計算出來的,整個螢幕扣除其它區塊所佔的位置,剩下的便是中央區塊的大小。

在範例中,每一個 ContentPane 都設定了邊框,並將 margin 和 padding 都設為 0px, 這是為了方便展示;實務上,應該根據需要來設定。