2013年12月8日 星期日

ExtJS 4 測試 : 頁籤面板 TabPanel

頁籤面板簡單好用, 直觀明瞭, 是我寫專案最愛用的的 UI 容器. ExtJS 的頁籤面板美觀易用, 由 Ext.tab.Panel 類別提供 (別名 Ext.TabPanel), 它是 Ext.panel.Panel 的子類別, 每一個頁籤都是一個 Ext.panel.Panel 物件, 同一時間只有其中一個是作用中頁籤 (active tab), 當點選其他頁籤時, 該頁籤就變成作用中頁籤了.

TabPanel 類別事實上乃一特殊化的 Panel 類別, 由標籤列與內容主體兩部分構成, 標籤列 (TabBar 物件) 事實上就是一組按鈕, 而內容主體則是一個 CardLayout 容器物件, 用來顯示與標籤對應之 HTML 內容. 標籤列預設位置是在上方, 透過設定可以改為下方或左右位置的標籤列. 此 TabPanel 類別的 API 文件詳見 :

http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.tab.Panel

以下就根據 API 文件來測試頁籤面板的常用功能. 要建立一個頁籤面板只要繼承 Ext.tab.Panel 類別, 並設定所需之 items 屬性即可, ExtJS 會自動在標籤切換時更換主體內容. 如下列範例 1 所示 :

測試範例 1 : http://mybidrobot.allalla.com/extjstest/tabpanel_1.htm [看原始碼]

      Ext.create("Ext.tab.Panel",{
        title:"工作日誌系統",
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });

此 處 renderTo 設為 Ext.getBody(), 此方法會傳回 body 元素 Ext.Element 物件, 亦即將頁籤面板直接描繪於 body 文件中. 當然若要繪製於文件的內部元素也是可以的, 這時 renderTo 就要設定為該元素的 id 字串, 如下面範例 1-1 所示 :

測試範例 1-1 http://mybidrobot.allalla.com/extjstest/tabpanel_1_1.htm [看原始碼]

  <div id="tabpanel" style="margin:15px;"></div>
  <script type="text/javascript">
    Ext.onReady(function() {
      Ext.create("Ext.tab.Panel",{
        title:"工作日誌系統",
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:"tabpanel"
        });
      });
  </script>

從上例可知, 預設情況下, 標籤列是在上方, 寬度占據整個頁面, 高度則依頁籤內容而定. 如果要將標籤列放在下方, 就要將 tabPosition 屬性設為 "bottom". 同時也可以分別用 width 與 height 屬性設定寬度與高度, 如範例 2 所示 :


      Ext.create("Ext.tab.Panel",{
        title:"工作日誌系統",
        width:300,
        height:100,
        tabPosition:"bottom",
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
renderTo:Ext.getBody()
        });






 

測試範例 2 : http://mybidrobot.allalla.com/extjstest/tabpanel_2.htm [看原始碼]

我們也可以將 tabPosition 改為 "left" 或 "right", 如下面範例 3 為左方標籤列 :

測試範例 3 : http://mybidrobot.allalla.com/extjstest/tabpanel_3.htm [看原始碼]



在範例 3 中, 我們把標籤改在左邊, 可見標籤文字都變成直排, 但要轉過來看, 不見得實用. 

接下來要測試兩個屬性 : closable (可關閉) 與 disabled (禁用), 這兩個屬性都是 true/false 布林值, 被設為 disable: true 的標籤將無法點選 (灰色), 而被設為 closable:true 的標籤, 其右邊會有一個 X, 按下去會關閉該標籤. 

測試範例 4 : http://mybidrobot.allalla.com/extjstest/tabpanel_4.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        items:[
          {title:"首頁",html:"首頁",closable:true},
          {title:"差勤",html:"差勤",closable:true},
          {title:"管理",html:"管理",disabled:true}
          ],
        renderTo:Ext.getBody()
        });


在上例中, 我們在 head 裡設定了 body 的樣式, 讓整個頁籤與瀏覽器邊框有 10px 的距離 :

  <style>
    body {padding:10px;}
  </style>

其實這可以用 style 屬性來設定, 如下列範例 5 所示 :

測試範例 5 : http://mybidrobot.allalla.com/extjstest/tabpanel_5.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"padding:10px;background-color:yellow;",
        items:[
          {title:"首頁",html:"首頁",closable:true},
          {title:"差勤",html:"差勤",closable:true},
          {title:"管理",html:"管理",disabled:true}
          ],
        renderTo:Ext.getBody()
        });


在上例中, 我們去除 head 中的 body {padding:10px} 樣式, 轉而在 TabPanel 物件中設定了 style 屬性, 還加上了 background-color 樣式. 

在以上範例中, 頁籤主體內容裡的文字緊貼著邊, 在視覺上不太協調, 其實這可以在每個頁籤項目中用 bodyPadding 屬性來設定內容與邊框之距離. 在下列範例 6 中, 同時也將 TabPanel 的 frame 屬性設為 true, 這樣頁籤主體內容外就會有一個邊框 :

測試範例 6 : http://mybidrobot.allalla.com/extjstest/tabpanel_6.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        frame:true,
        items:[
          {title:"首頁",html:"首頁",bodyPadding:10},
          {title:"差勤",html:"差勤",bodyPadding:10},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        }); 



在上例中可見頁籤內容外有約 2px 的邊框出現, 且第一個與第二個頁籤之文字內容與邊框之間也有 10px 的空間, 而第三個頁籤則無. 但是如果每一個頁籤都要這樣設 bodyPadding 屬性很麻煩, 這可以用 defaults 屬性作一次設定 :

測試範例 7 : http://mybidrobot.allalla.com/extjstest/tabpanel_7.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        frame:true,
        defaults:{bodyPadding:10, closable:true},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });  .

上例中每一個頁籤 (items) 都會有一個 bodyPadding:10 的與 closable:true 屬性值, 但不用在 items 中一一設定, 其實只要是每一個頁籤都一樣的屬性值, 都可以全部統一於 defaults 中設定 (就好像是乘法的分配率一樣). 



前面兩例中的 bodyPadding 屬性只能設定內容與邊框間之留白, 如果要設定內容區的其他樣式該怎麼做? 其實有一個更一般的屬性 bodyStyle 可用, 如下面範例 7-1 所示 :

測試範例 7-1 : http://mybidrobot.allalla.com/extjstest/tabpanel_7_1.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        frame:true,
        defaults:{bodyStyle:"padding:15px;background-color:yellow;"},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        }); 



但是, 並非每一個樣式都能生效, 例如 height 就不行, 如下列範例 7-2 所示 :


測試範例 7-2http://mybidrobot.allalla.com/extjstest/tabpanel_7_2.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        frame:true,
        defaults:{bodyStyle:"width:100%;height:100%;background-color:yellow;"},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        }); 


我們刻意把整個 TabPanel 的 height 屬性拿掉, 然後在 bodyStyle 裡加入 height:100%, 希望頁籤的主體高度可以塞滿整個瀏覽器, 但是這樣設定是無效的. 而 bodyStyle 中的 width:100% 是有效的, 但是若同時有設定TabPanel 的 width 屬性的話, bodyStyle 中的 width 會被忽略. 注意, TabPanel 的 width 與 height 都是數值 (px), 所以不能將其設為 height:100%, 這樣會出現 "Unexpected token" 錯誤.

要讓頁籤面板高度填滿瀏覽器必須使用 Ext.container.Viewport 容器, 並且將 layout 設定為 "fit" 才行, 如下列範例 7-3 所示 :

測試範例 7-3http://mybidrobot.allalla.com/extjstest/tabpanel_7_3.htm [看原始碼]

      Ext.create("Ext.container.Viewport",{
        layout:"fit",
        items:{
          xtype:"tabpanel",

          title:"工作日誌系統",
          frame:true,
          defaults:{bodyPadding:10, closable:true},
          items:[{title:"首頁",html:"首頁"},
                     {title:"差勤",html:"差勤"},
                     {title:"管理",html:"管理"}]
          },
        renderTo:Ext.getBody()


此處我們使用 xtype 來建立頁籤面板, 並作為一個元件放進 Viewport 容器之中, Viewport 是一個能撐滿整個瀏覽器的容器, 而且當瀏覽器大小調整時, Viewport 內的元件也會隨同調整.

接下來我們來測試一下 plain 這個屬性, 用來設定是否要顯示頁籤列的背景圖, 它是一個布林值, 預設為 false, 如果設為 true, 頁籤列的背景圖就沒有了, 如下列範例 8 所示 :

測試範例 8 : http://mybidrobot.allalla.com/extjstest/tabpanel_8.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        frame:true,
        plain:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });



可見將 plain 設為 true 後, 標籤列後面那個方塊就不見了. 這通常是把頁籤面板放到其他容器, 作為其中一個組件時, 為了讓畫面不要那麼複雜才會用到, 故通常也不需要標題, 亦即 title 屬性也不需設定, 如下列範例 9 所示 :

測試範例 9 : http://mybidrobot.allalla.com/extjstest/tabpanel_9.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        width:300,
        height:200,
        frame:true,
        plain:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });



上例中把 title 拿掉, 同時 plain 設為 true 畫面看起來就較為單純了.

以上在頁籤列項目中, 我們僅指定了 items 屬性的 title 與 html 兩個子屬性, 接下來我們要用到另一個屬性 tabConfig 來測試標籤列的提示功能. 前面我們已知頁籤列 TabBar 其實是按鈕組成的, 其文字通常很簡約, 能不能讓使用者滑鼠移到頁籤上就跳出一個說明文字呢? 可以的, ExtJS 有一個直接繼承自根類別 Ext.Base 的提示類別 Ext.tip.QuickTipManager 類別 (別名是 Ext.QuickTips, 注意是複數), 可以很方便為每個頁籤設定提示文字. 此類別用法詳見 API :

http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.tip.QuickTipManager

下面範例 10 演示如何為頁籤加上提示 :

測試範例 10 : http://mybidrobot.allalla.com/extjstest/tabpanel_10.htm [看原始碼]

      Ext.tip.QuickTipManager.init();
      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        items:[
          {title:"首頁",
           html:"首頁",
           tabConfig:{
             tooltip:"This ia Home!"
             }
           },
          {title:"差勤",
           html:"差勤",
           tabConfig:{
            tooltip:{title:"Attendance",text:"This is Attendance!"}
            }
           },
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });



在上例中, 在使用 QuickTips 的提示功能之前, 一定要先呼叫其 init() 方法. 然後於每個頁籤項目中以 tabConfig 屬性設定 tooltip 子屬性. 此 tooltip 之值可以為字串, 就是要在頁籤按鈕上顯示的提示字串, 如上面左圖所示; 也可以是物件, 其中 title 是粗體的標題, text 是提示字串, 如上面右圖所示. 由於 Ext.tip.QuickTipManager 名字太長了, 它有一個別名 Ext.QuickTips, 所以上面程式碼中, 初始化也可以寫成 Ext.QuickTips.init(). 

其實頁籤物件的 items 屬性的 tabConfig 還可以放別的屬性, 例如在頁籤上放置圖形. 這要用到 iconCls 屬性, 如下範例 10-1 所示 :

測試範例 10-1 : http://mybidrobot.allalla.com/extjstest/tabpanel_10_1.htm [看原始碼]

首先要準備一個圖檔, 例如表示首頁的 home.gif, 然後在 style 中設定 Class :

    .icon-home {
       background-image: url('home.gif');
       background-repeat: no-repeat;
       }

然後在首頁的 item 中, 於 tabConfig 中添加 iconCls 屬性 :

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",
           html:"首頁",
           tabConfig: {iconCls: 'icon-home'}
           },
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });



如上圖所示, 在首頁頁籤左側出現了指定之圖示. 事實上這個圖示是可以在程式中動態變更的, 這要用到頁籤物件的 setIconCls 方法, 如下面範例 10-2 所示

測試範例 10-2http://mybidrobot.allalla.com/extjstest/tabpanel_10_2.htm [看原始碼]

為了動態更換圖示, 我們要再準備一個圖示 home-2.gif, 並設定其 class 危 icon-home-2 :

    .icon-home {
       background-image: url('home.gif');
       background-repeat: no-repeat;
       }
    .icon-home-2 {
       background-image: url('home-2.gif');
       background-repeat: no-repeat;
       }


然後添加一個 tbar 屬性, 這屬性來自 Ext.panel.Panel 類別, 預設元件類型 xtype 為 button :

       var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        tbar: [{
          text:"切換圖示",
          handler: function(){
            tp.items.get(0).tab.setIconCls('icon-home-2');
            }
          }],
        items:[
          {title:"首頁",
           html:"首頁",
           tabConfig: {iconCls: 'icon-home'},
           },
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });


 
在上例中, 我們同時測試一下 tbar 屬性 (top bar), 此屬性會在頁籤列下面多一個工具列, 可以放置按鈕等元件. 此處我們放置了一個切換圖示鈕, 並設置其 click 事件的處理器, 變更首頁頁籤的圖示. 

也可以把頁籤的標題 title 放在 tabConfig 屬性中, 如果頁籤與 tabConfig 中同時都有設定 title 屬性, 這樣 tabConfig 的 title 將會覆蓋頁籤的 title (即使 tabConfig 先宣告也是一樣), 也可以用 width 設定頁籤按鈕的寬度, 如下面範例 10-3 所示 :

測試範例 10-3 : http://mybidrobot.allalla.com/extjstest/tabpanel_10_3.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        items:[
          {html:"首頁",tabConfig:{title:"Home",width:150},title:"首頁"},
          {title:"差勤",html:"差勤"},
          {html:"管理",tabConfig:{title:"管理"}}
          ],
        renderTo:Ext.getBody()
        });

上例中的首頁頁籤裡, tabConfig 的 title 屬性值覆蓋了頁籤物件本身的 title 屬性, 顯示 "Home"; 而管理頁籤中, 我們沒有設定頁籤物件的 title 屬性值, 而改在 tabConfig 物件中設定, 效果相同.

接著要測試 minTabWidth 屬性, 這是用來限制頁籤寬度的, 如下列範例 11 所示 :

測試範例 11 : http://mybidrobot.allalla.com/extjstest/tabpanel_11.htm [看原始碼]


      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        minTabWidth:70,
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"例行檢查",html:"例行檢查"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });



可見當設定 minTabWidth (單位 px) 後, 每一個頁籤寬度都至少為 70px 了, 如果沒設定的話, 預設頁籤寬度是自動調整到與文字同寬. 同時頁籤寬度變大會導致後面的頁籤看不到, ExtJS 會自動在左右兩邊出現前進後退按鈕來滑動頁籤.

也可以用 
maxTabWidth 屬性來限制頁籤的最大寬度, 如下面範例 12 所示 :

測試範例 12 : http://mybidrobot.allalla.com/extjstest/tabpanel_12.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        maxTabWidth:50,
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"例行檢查",html:"例行檢查"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        }); 



可見較長文字的頁籤會被刪節成 "xx ..." 以符合最大寬度之限制. 

上面範例中, 頁籤面板預設都是開啟第一個頁籤, 如果想要開啟特定頁籤該怎麼做? 這可以用 activeTab 屬性來設定 (索引從 0 起算). 另外, 當頁籤內容之長度超過 height 屬性值時, 超過部分是不會顯示的, TabPanel 預設不會在右邊出現滑動桿 (scroll bar), 這必須在項目中把 autoScroll 屬性設為 true 才行. 我們上面所有頁籤都是固定不動的, 如果要讓整個頁籤面板可以拖曳, 就要將 draggable 屬性設為 true. 以上三屬性測試如下列範例 13 所示 :

測試範例 13 : http://mybidrobot.allalla.com/extjstest/tabpanel_13.htm [看原始碼]

      var w="w<br>w<br>w<br>w<br>w<br>w<br>w<br>w<br>w<br>w<br>w<br>w";
      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        draggable:true,
        activeTab:1,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",html:w,autoScroll:true},
          {title:"差勤",html:w},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        }); 


上例中網頁開啟時, 因為 activeTab 設為1 (注意, activeTab 的值為 0 起始的頁籤索引), 所以預設顯示的作用中 (active) 頁籤是第二頁, 而其內容長度高於 height 的 200px, 但是後面的 w 字元卻都無法顯示. 而右圖切到第一頁籤, 由於該項目設定了 autoScroll:true, 故可看到右方出現滑動桿, 可以看到完整的頁籤內容. 如果每一個頁籤內容都要在內容長度超過高度時自動出現滑動桿, 那麼可以把 autoScroll:true 放在 defaults 屬性中. 注意, autoScroll 是 items 物件的屬性, 不是 TabPanel 物件的屬性, 只能放在 defaults 中, 不要放錯位置.

上例是在建立 TabPanel 物件時靜態地指定作用中頁籤, 如果要在頁籤面板建立後用程式動態地去改變該怎麼做? 這就要用到 setActiveTab() 方法了. 在下面範例 14 中, 同時要用到 TabPanel 的另外一個好用的屬性 buttons, 可以在頁籤上添加按鈕來控制頁籤.

測試範例 14 : http://mybidrobot.allalla.com/extjstest/tabpanel_14.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"新增",handler:addTab}],
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        }); 
      function addTab() {
        var idx=tp.items.length + 1;
        var newItem={title:"tab" + idx,html:"tab" + idx};
        var newTab=tp.add(newItem);
        tp.setActiveTab(newTab);
        }


上例中, buttons 屬性所設定之按鈕預設會出現在頁籤面板下方 (bbar 物件) 的右邊, 其 click 事件處理函式會新增一個新的頁籤, 並且將新頁籤設為作用中 (active). 此處因為要操作頁籤, 因此要將建立的頁籤面板物件在 create 後傳回給變數 tp, 用 tp.items 便取得所有頁籤物件集合 (陣列), 其長度 tp.items.length 就是目前已有之頁籤數目, 加 1 後做為新頁籤索引以便製作頁籤項目, 再呼叫頁籤面板的 add 方法將此新項目加入面板中, 並呼叫 setActiveTab() 方法將新頁籤設為作用中.

上面的按鈕預設是在右方, 可以用 buttonAlign 屬性改為左方 (設為 "left") 或中間 ("center") 改為左方, 在下面範例 15 中, 我們同時要測試 TabPanel 的 remove() 方法, 也要用到 itemId 或 id 屬性.

測試範例 15 : http://mybidrobot.allalla.com/extjstest/tabpanel_15.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"新增",handler:addTab},
                     {text:"刪除",handler:delTab}],
        buttonAlign:"left",
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });
      function addTab() {
        var idx=tp.items.length + 1;
        var newItem={title:"tab" + idx,itemId:"tab" + idx,html:"tab" + idx};
        var newTab=tp.add(newItem);
        tp.setActiveTab(newTab);
        }
      function delTab() {
        var idx=tp.items.length;
        var delTab=tp.remove("tab" + idx, true);
        }


在上例中, 我們在面板中放置了兩個按鈕, 因為 buttonAlign 設為 "left", 因此他們都靠左對齊了. 在 事件處理函式 addTab 中, 我們給新增的頁籤項目添加了 itemId 屬性, 因為在刪除新增頁籤時, 需要用此來識別. 當然用 id 也是可以的, 但建議使用 itemId, 因為當系統複雜時, 必須全域唯一的 id 可能會跟別的元件衝突而導致功能錯誤, 而用 itemId 則不會有此問題.

在範例 15 中, 按下刪除鈕會反序刪除新增的頁籤, 這是靠呼叫頁籤面板的 remove() 方法達成的, 它可以傳入兩個參數, 第一個是要刪除的元件 (id 或 itemId), 第二個是 autoDestroy (true/false), 這是選項. 當全部新增頁籤都被刪除只剩下原來 3 個頁籤時, 因為找不到 tab3, 所以就不會繼續刪除, 但也不會有 error.

注意, 存取 itemId 時要用頁籤面板物件呼叫 getComponent() 方法, 而存取 id 則是直接使用 Ext 類別的靜態方法 getCmp(), 如果顛倒了會出現錯誤. 下列範例 15-1 說明 id 與 itemId 之用法 :

測試範例 15-1 : http://mybidrobot.allalla.com/extjstest/tabpanel_15_1.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"顯示首頁",handler:showHome},
                     {text:"顯示管理",handler:showAdmin}],
        items:[
          {title:"首頁",html:"首頁",id:"home"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理",itemId:"admin"}
          ],
        renderTo:Ext.getBody()
        });
      function showHome(){
        tp.setActiveTab(Ext.getCmp("home"));
        }
      function showAdmin(){
        tp.setActiveTab(tp.getComponent("admin"));
        }

在範例 15-1 中, 我們用兩個按鈕分別將首頁與管理頁籤設成作用中, 但方式不同, 首頁頁籤裡指定 id 屬性, 而管理頁籤裡則指定 itemId 屬性. 在事件處理函式中存取這兩個頁籤時須分別呼叫 Ext.getCmp() 與 tp.getComponent() 方法.

OK, 回到刪除頁籤的問題, 上面範例 15 只能刪除新增的頁籤, 如果要刪除目前作用中的頁籤該怎麼做? 這就需要用到 getActiveTab() 方法了, 如範例 15-2 所示 :

測試範例 15-2 : http://mybidrobot.allalla.com/extjstest/tabpanel_15_2.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"新增",handler:addTab},
                       {text:"刪除",handler:delTab}],
        buttonAlign:"left",
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });
      function addTab() {
        var idx=tp.items.length + 1;
        var newItem={title:"tab" + idx,html:"tab" + idx};
        var newTab=tp.add(newItem);
        tp.setActiveTab(newTab);
        }
      function delTab() {
        var tab=tp.getActiveTab();
        Ext.Msg.confirm("確認","確定要刪除頁籤 '" + tab.title + "'?",
          function(btn){
            if (btn=="yes"){var delTab=tp.remove(tab);}
            }
          );
        }

在上例中我們呼叫 getActiveTab() 取得作用中頁籤物件, 這樣就可以存取頁籤的屬性, 例如 title. 然後用一個確認框來詢問是否真的要刪除頁籤. 若按下 Yes 按鈕就呼叫 remove() 方法刪除之.

除了 add() 以外, 還有一個跟新增頁籤有關的方法 insert(index, tabComp), 可以指定頁籤物件要插入頁籤列成為索引 index, 如下範例 15-3 所示 :

測試範例 15-3 : http://mybidrobot.allalla.com/extjstest/tabpanel_15_3.htm [看原始碼]

      function addTab() {
        var idx=tp.items.length + 1;
        var newItem={title:"tab" + idx,html:"tab" + idx};
        var newTab=tp.insert(1,newItem);
        tp.setActiveTab(newTab);
        }

新增加的頁籤都會插入索引 1 的位置, 即首頁頁籤的後面.

下面範例 16 接著測試 items 頁籤項目的事件監聽屬性 listeners, 可以指定頁籤事件的監聽器.

測試範例 16 : http://mybidrobot.allalla.com/extjstest/tabpanel_16.htm [看原始碼]

      Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10,closable:true},
        items:[
          {title:"首頁",html:"首頁",
           listeners:{
             beforeclose:function(){return false},
             beforedeactivate:function(){Ext.Msg.alert("資訊","離開首頁");}
             }
           },
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });


在上例中, 我們在 defaults 屬性中設定 closable:true, 這樣每一個頁籤按鈕都會出現一個關閉小按鈕, 然後在首頁頁籤項目中, 添加一個 listeners 屬性, 設定監聽頁籤的 beforeclose 與 beforedeactivate 這兩個事件. 其中 beforeclose 屬性若為 fasle, 則頁籤關閉動作將不會執行, 所以即使首頁頁籤為 closable, 但按 X 鈕卻關不掉. 而 befroedeactivate 事件會在切換頁籤, 原作用中頁籤之狀態要改為非作用中時觸發, 上面我們是讓它去呼叫一個顯示訊息框的回呼函式, 每次從首頁要切換到別頁時就會顯示訊息框.

上面範例 10-2 我們測試過利用 tbar 工具列來切換頁籤圖示, 這裡我們改用頁籤的 listeners 來控制頁籤圖示之切換, 當從首頁切換到別頁時更換圖示; 切回首頁時恢復原來的圖示, 這要用到 beforedeactivate 與 beforeactivate 兩個事件, 如下範例 16-1 所示 :

測試範例 16-1http://mybidrobot.allalla.com/extjstest/tabpanel_16_1.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",
           html:"首頁",
           tabConfig: {iconCls: 'icon-home'},
           listeners:{
             beforedeactivate:changeIconDeactive,
             beforeactivate:changeIconActive
             }
           },
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });
      function changeIconDeactive() {
        tp.items.getAt(0).tab.setIconCls("icon-home-2");
        }
      function changeIconActive() {
        tp.items.getAt(0).tab.setIconCls("icon-home");
        }

下面範例 17 要測試頁籤物件的 setDiasabled() 方法. 首先用 buttons 屬性放置兩個按鈕, 左邊那個 "禁能" 鈕會讓作用中的頁籤變成不可用 (disabled), 而右邊那個 "致能" 鈕可以讓全部頁籤都變成可用 (enabled).

 測試範例 17 : http://mybidrobot.allalla.com/extjstest/tabpanel_17.htm [看原始碼]

    var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"禁能",handler:setDisabled},
                     {text:"致能",handler:setAllEnabled}
                 ],
        items:[
          {title:"首頁",html:"首頁",itemId:"home"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });
      function setDisabled(){
        var activetab=tp.getActiveTab();
        activetab.setDisabled(true);
        }
      function setAllEnabled(){
        for (var i=0; i<tp.items.length; i++) {
             tp.items.getAt(i).setDisabled(false);
             }
        }


上例中按下禁能鈕時, 先用 getActiveTab() 取得作用中頁籤物件, 再呼叫此頁籤之 setDisabled() 方法, 傳入 true 表示要 disable 此頁籤. 當按下致能鈕時, 先用頁籤面板的 items 屬性取得所有頁籤物件陣列, 其 length 屬性即為全部頁籤個數, 再用迴圈拜訪每一個頁籤物件, 呼叫 items 陣列之 getAt() 方法 (或 get 方法亦可) 即可取得指定索引位置之頁籤物件, 再呼叫 setDisabled() 方法, 傳入 false 表示要 enable 此頁籤, 這樣那些被禁能的頁籤就恢復為可用狀態了.

接著要測試的是頁籤物件的 hide() 與 show() 這兩個方法, 他們分別會將頁籤之按鈕或內容隱藏與顯示, 如下列範例 18 所示 :

測試範例 18http://mybidrobot.allalla.com/extjstest/tabpanel_18.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"隱藏",handler:hideActive},
                     {text:"顯示",handler:showActive},
                     {text:"顯示全部",handler:showAll}],
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });
      function hideActive() {
        var activetab=tp.getActiveTab();
        activetab.tab.hide();
        activetab.hide();
        }
      function showActive() {
        var activetab=tp.getActiveTab();
        activetab.tab.show();
        activetab.show(); //無效, why?
        }
      function showAll() {
       for (var i=0; i
             tp.items.get(i).tab.show();
             tp.items.get(i).show();
             }
        }



上例中, 以 activeTab() 可以取得作用中的頁籤物件, 呼叫其 hide() 方法會隱藏頁籤內容, 但頁籤按鈕不受影響; 呼叫其 show() 方法照官網說明應該顯示頁籤內容, 但卻無效, why? 如果要控制頁籤按鈕的顯示與否, 要用頁籤物件的 tab 屬性先取得其按鈕物件, 再呼叫 show() 或 hide() 方法. 按下隱藏鈕時, 作用中頁籤的按鈕與內容部份會被隱藏, 按顯示則僅恢復顯示剛剛被隱藏的頁籤按鈕而已, 內容部分沒有恢復. 按顯示全部則所有被隱藏之頁籤都恢復顯示.

事實上 Ext.tab.Tab 類別還有一個 setVisible() 方法可以達成顯示與隱藏頁籤的功能, 我們把上例改為如下範例 18-1 :

測試範例 18-1 : http://mybidrobot.allalla.com/extjstest/tabpanel_18_1.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:350,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"隱藏",handler:hideActive},
                      {text:"顯示",handler:showActive},
                      {text:"替換",handler:toggleState},
                      {text:"顯示全部",handler:showAll}],
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        }); 
      function hideActive() {
        var activetab=tp.getActiveTab();
        activetab.tab.setVisible(false);
        activetab.setVisible(false);
        }
      function showActive() {
        var activetab=tp.getActiveTab();
        activetab.tab.setVisible(true);
        activetab.setVisible(true); //無效, why?
        }
      function toggleState() {
        for (var i=0; i
             var tab=tp.items.get(i);
             var tabbtn=tab.tab; //取得頁籤按鈕
             tabbtn.setVisible(!tabbtn.isVisible());
             tab.setVisible(!tab.isVisible());              }
        }
      function showAll() {
       for (var i=0; i
             var tab=tp.items.get(i);
             var tabbtn=tab.tab;
             tabbtn.setVisible(true);
             tab.setVisible(true);
             }
        }

在上例中, 我們把 show/hide 改成用 setVisible 去做, 同時加了一個 "替換" 按鈕, 按下去就顯示已經隱藏的頁籤, 把目前顯示的隱藏起來. 但是同樣的, 頁籤內容的 setVisible(true) 跟 show() 一樣都無效, 不知何故也.

以上所有範例中, 頁籤內容都是在 items 的 html 屬性中設定, 若要在程式中動態加以改變該怎麼做呢? 這就要用到 update() 方法了, 如下列範例 19 所示 :

測試範例 19 : http://mybidrobot.allalla.com/extjstest/tabpanel_19.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"更新",handler:changeContent}],
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });
      function changeContent() {
        var activetab=tp.getActiveTab();
        activetab.update("<a href='http://tw.yahoo.com' target='_blank'>Yahoo</a>");
        }



上例中, 按下更新按鈕, 事件處理函式先用 getActiveTab() 取得作用中頁籤物件, 再呼叫其 update() 方法更新頁籤內容, 內容可以是純文字/HTML 字串, 或者是 ExtJS 元件. 事實上 update() 方法 還有兩個備選參數如下 :

update(htmlOrData,[loadScripts],[callback])

其中 loadScripts 為布林值 (預設 false), 只有當第一參數為 HTML 時才有效, 而 callback 為更新後之回呼函式. 下面範例 19-1 即測試其功能 :

測試範例 19-1 : http://mybidrobot.allalla.com/extjstest/tabpanel_19_1.htm [看原始碼]

      function changeContent() {
        var activetab=tp.getActiveTab();
        var html="<a href='http://tw.yahoo.com' target='_blank'>Yahoo</a>" +
                      "<script>alert('更新完成!')<\/script>";
        activetab.update(html,true,callback);
        }
      function callback() {
        Ext.Msg.alert("訊息","更新完成!");
        }

在上例中, 我們將 loadScript 設為 true, 並指定回呼函式, 因此更新頁籤內容時, 其中的 script 會被載入, 並被立即執行, 因 alert() 有強制性, 須等按下 alert() 之確定後才會執行回呼程式. 如果把 localScript 設為 false, 則不會載入 script, 因此會直接執行回呼程式.

上面兩個範例是在程式裡面更新頁籤內容, 如果要從後端伺服器載入內容該怎麼做? 這要在頁籤物件中設定 autoLoad 屬性, 如下面範例 20 所示. 注意, 以下範例因為要從同網域中載入檔案, 所以都只能在伺服器上執行, 不能在本地執行.:

測試範例 20 : http://mybidrobot.allalla.com/extjstest/tabpanel_20.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤",
          autoLoad:{url: "tabpanel_autoload_1.php"}
          },
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });

在此例中, 我們在第二個頁籤加入 autoLoad 物件, 並設定其 url 屬性為同網域中的 php 檔案, 其內容如下 :

<?php
date_default_timezone_set("Asia/Taipei");  
echo date("Y-m-d H:i:s")." 載入<br>";
?>

亦即它會回應目前的台北時間, 如下圖所示 :


當切到差勤頁籤時,  就會從伺服器載入該網頁, 並覆蓋掉 html 屬性中原本的 "差勤" 兩字. 這 autoLoad 屬性為一個物件, 亦即還可以放其他屬性, 例如可以用 params 屬性傳遞參數給後端伺服器, 並且設定 scripts 為 true, 如下列範例 20-1 所示 :

測試範例 20-1 : http://mybidrobot.allalla.com/extjstest/tabpanel_20_1.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤",
            autoLoad:{
              url:'tabpanel_autoload_2.php',
               scripts:true,
               params:{param1:"Hello ",param2:"World!"}
              }
           },
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });

而要載入的後端網頁 tabpanel_autoload_2.php 如下 :

<?php
date_default_timezone_set("Asia/Taipei");  
echo date("Y-m-d H:i:s")." 載入<br>";
echo $_REQUEST["param1"].$_REQUEST["param2"];
?>
<script>
  alert("autoLoad 載入完成!");
</script>

結果如下圖所示, 所傳遞之參數有送回來, 但是 scripts 卻沒作用, why?


當然, 如果把參數附在 url 字串後面 (?param1=Hello&param1=World), 使用 GET 方式而不用 params 屬性也是可以的.

載入檔案除了 autoLoad 屬性外, 也可以使用 loader 屬性, 如下列範例 21 所示.

測試範例 21 : http://mybidrobot.allalla.com/extjstest/tabpanel_21.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤",
           loader:{
             url:"tabpanel_autoload_1.php",
             autoLoad:true,
             scripts:true
             }
           },
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });


注意, 這裡 loader 物件的 autoLoad 屬性一定要設為 true (不設的話預設為 false), 否則不會載入指定之檔案. 此例中的 Script 也是沒有執行.

以上範例不管是使用 loader 還是 autoLoad, 都只會載入檔案一次, 因為切到別頁再切回來時, 仍然顯示同樣的時間, 可見沒有再次載入. 如果要在每次切換頁籤時都重新載入檔案該怎麼做? 這必須用 listeners 去監聽 activate 事件, 當頁籤變成作用中狀態時, 就讓 tab 物件重新載入一次, 如下列範例 22 所示 :

測試範例 22 : http://mybidrobot.allalla.com/extjstest/tabpanel_22.htm [看原始碼]

      var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤",
           loader: {
             url:"tabpanel_autoload_2.php",
             autoLoad:true,
             loadMask:true,
             scripts:true
             },
           listeners:{activate:function(tab){tab.loader.load();}}
           },
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });

由此例可發現, 如果在 listeners 中觸發載入檔案, 這樣 script 就可以執行了. 此處我們監聽 activate 事件, 因此每次切換到 "差勤" 頁籤時, 就會重新自伺服器載入 (可觀察時間已有改變), script 也會再次執行.

在上例中, 我們在 loader 物件裡添加了 loadMask:true 屬性, 這樣在載入檔案時, 會顯示 "Loading ... " 的對話框, 直到載入結束, 若未設此屬性 (預設 false), 就不會顯示.

如果我們不想每次都從伺服器載入, 只想於第一次載入後就在本機中執行, 這樣就不能監聽 activate 事件, 要改為 render 事件, 如下範例 22-1 所示 :

測試範例 22-1 : http://mybidrobot.allalla.com/extjstest/tabpanel_22_1.htm [看原始碼]

listeners:{render:function(tab){tab.loader.load();}}

但這樣一來 script 就只有在初次從伺服器載入時才會執行一次, 之後切換到差勤頁籤就不會執行 script 了.

在網路上看到這篇 :

http://www.cnblogs.com/letao/archive/2012/05/18/2507234.html

想說照其作法測試看看, 我把上面範例 15 拿來改成下面範例 22-2  :

測試範例 22-2 : http://mybidrobot.allalla.com/extjstest/tabpanel_22_2.htm [看原始碼]

       var tp=Ext.create("Ext.TabPanel",{
        title:"工作日誌系統",
        width:300,
        height:200,
        style:"margin:10px;",
        frame:true,
        defaults:{bodyPadding:10},
        buttons:[{text:"新增",handler:addTab},
                     {text:"刪除",handler:delTab}],
        items:[
          {title:"首頁",html:"首頁"},
          {title:"差勤",html:"差勤"},
          {title:"管理",html:"管理"}
          ],
        renderTo:Ext.getBody()
        });
      function addTab() {
        var idx=tp.items.length + 1;
        var newItem={title:"tab" + idx,
                     itemId:"tab" + idx,
                     loader:{url:"tabpanel_autoload_3.php",
                             autoLoad:true,
                             params:{param1:"Hello ",param2:"World!"},
                             autoMask:true,
                             scripts:true
                             },
                     listeners:{render:function(tab){tab.loader.load();}}
                     };
        var newTab=tp.add(newItem);
        tp.setActiveTab(newTab);
        }
      function delTab() {
        var idx=tp.items.length;
        var delTab=tp.remove("tab" + idx, true);
        }
      });

而載入的 php 檔案如下, 這回我們將 alert 放在函式中, 利用按鈕來觸發, 而不是放在頂層 (只在載入時執行一次) :

<?php
header('Content-type: text/html; charset=utf-8');
date_default_timezone_set("Asia/Taipei");  
echo date("Y-m-d H:i:s")." 載入<br>";
$params=$_REQUEST["param1"].$_REQUEST["param2"];
echo '<button onclick="javascript:show()">確定</button>'.
        '<script>'.
        'function show() {'.
        '  alert("'.$params.'");'.
        '  }'.
        '</script>';
?>


按下新增鈕就會增加一個頁籤, 並從伺服器載入含有 script 的內容, 因為 scripts 設為 true, 因此按確定鈕會呼叫函式 show(), 顯示 "Hello World!". 由於監聽的是 render 事件, 僅新增頁籤時才載入檔案, 因此切換頁籤時間不會變化.

OK, 以上便是近三周來對 TabPanel 的測試, ExtJS 真難搞啊.

沒有留言 :