2020年9月1日 星期二

jQuery UI 學習筆記 (十二) : 折疊選單 (Accordion)

最近因為工作上的需要, 重新改寫作業自動化資訊系統, 採用 jQuery UI 作為操作介面, 其中指令集部分使用了折疊選單 Accordion (又稱手風琴選單) 呈現, 所以順便把筆記也寫一寫, 方便以後查考.

Accordion API 與教學文件參考 :

https://jqueryui.com/accordion/
http://api.jqueryui.com/accordion/

本篇測試參考了如下書籍 :
  1. jQuery UI 使用者介面設計 (歐萊里, Studio Tib. 譯)
  2. jQuery UI 與 Plugin 開發實戰 (悅知文化, 吳哲穎譯)
  3. jQuery 全能權威指南 (上奇, 張亞飛)
  4. Pro jQuery 2.0 2nd ed. (Apress, Freeman Adam)
  5. jQuery UI in Action (Manning, TJ Vantoll)
  6. jQuery 應用程式設計極速上手 (上奇, 羅友志譯)
  7. 打造 jQuery 網頁風暴 (張子秋, 佳魁)
折疊選單與頁籤面板類似, 都是用來呈現分類資料的容器, 具有節省網頁版面空間的功用, 只不過頁籤面板為橫向切換, 內容由左向右排列; 而折疊選單是直向切換, 內容由上而下排列. 點擊折疊選單的標題會開啟並顯示該標題之內容, 而原先開啟的標題內容則自動收合, 預設只有一個標題會開啟. 關於頁籤面板參考 :

jQuery UI 學習筆記 (四) : 頁籤面板 (Tabs)

折疊選單的網頁架構與頁籤面板一樣也是使用兩層 div 套疊, 它們的外層 div 元素只是個殼, 內層的 div 元素才是放內容的容器. 兩者在結構上的差別是, 頁籤面板使用 ul-li 元素建構頁籤, 而折疊選單則用 p 或 h1~h6 元素建構標題; 在功能上頁籤面板支援使用 Ajax 動態載入內容, 而折疊選單則不支援.

下面是一個三列折疊選單的網頁結構, 使用 h2 包覆標題內容 (其實不管是使用 h1~h6 哪一個元素, 標題的字大小都一樣)  :

<div id="accordion">
  <h2>第 1 列標題</h2>
  <div>第 1 列內容</div>
  <h2>第 2 列標題</h2>
  <div>第 2 列列內容</div>
  <h2>第 3 列標題</h2>
  <div>第 3 列內容</div>
</div>

然後在 Javascript 程式碼中取得外層 div 元素後呼叫 accordion() 即可 :

$("accordion").accordion()

例如 :


測試 1 : 折疊選單結構測試 (1) [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2>第 1 列標題</h2>
      <div id="row1">第 1 列內容</div>
      <h2>第 2 列標題</h2>
      <div id="row2">第 2 列內容</div>
      <h2>第 3 列標題</h2>
      <div id="row3">第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion();
        });
    </script>
  </body>
</html>

結果如下 : 




除了使用 h1~h6 元素外, 也可以用 p 元素來包覆標題內容 :  

<div id="accordion">
  <p>第 1 列標題</p>
  <div id="row1">第 1 列內容</div>
  <p>第 2 列標題</p>
  <div id="row2">第 2 列列內容</div>
  <p>第 3 列標題</p>
  <div id="row3">第 3 列內容</div>
</div>

例如下列範例 2 : 



<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <p>第 1 列標題</p>
      <div>第 1 列內容</div>
      <p>第 2 列標題</p>
      <div>第 2 列列內容</div>
      <p>第 3 列標題</p>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion();
        });
    </script>
  </body>
</html>

也可以使用 div 元素包覆標題內容 : 

<div id="accordion">
<div>第 1 列標題</div>
<div>第 1 列內容</div>
<div>第 2 列標題</div>
<div>第 2 列內容</div>
<div>第 3 列標題</div>
<div>第 3 列內容</div>
</div>

效果如下列範例 3 所示 :



<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <div>第 1 列標題</div>
      <div>第 1 列內容</div>
      <div>第 2 列標題</div>
      <div>第 2 列內容</div>
      <div>第 3 列標題</div>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion();
        });
    </script>
  </body>
</html>

不過有些書的範例是將標題內容先用 a 元素包覆後再用 h1~h6 或 p 元素包覆 : 

<div id="accordion">
  <h2><a href="#">第 1 列標題</a></h2>
  <div>第 1 列內容</div>
  <h2><a href="#">第 2 列標題</a></h2>
  <div>第 2 列內容</div>
  <h2><a href="#">第 3 列標題</a></h2>
  <div>第 3 列內容</div>
</div>

注意, 超連結 a 元素的 href 屬性須用 "#" (表示沒有連外). 



<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2><a href="#">第 1 列標題</a></h2>
      <div>第 1 列內容</div>
      <h2><a href="#">第 2 列標題</a></h2>
      <div>第 2 列內容</div>
      <h2><a href="#">第 3 列標題</a></h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion();
        });
    </script>
  </body>
</html>

內容有用 a 元素包覆的效果與沒有的看似差不多, 但仔細看還是有差異, 沒有 a 元素的標題在開啟後總是會有黑邊框; 而有 a  元素的則幾乎沒有. 官網範例沒有先用 a 元素包覆. 

以上範例在呼叫 accordion() 時沒有傳入參數, 這樣會以預設值 (例如開啟第一列標題等) 初始化折疊選單, 進階用法可以傳入一個選項物件來進行設定, 常用的選項物件屬性如下表 : 


 常用設定屬性 說明
 active 預設開啟之標題索引 (0 起始, 預設 0), 亦可為 true/false, 參考 collapsible
 aninmate 展開/收合時之動畫設定, true=有動畫, false=無動畫, 或用數值設定毫秒數
 collapsible 是否可收合 (預設 false), 當 true 時, 若 active=false 則全部收合
 disabled 是否將折疊選單禁能 true/false (預設)
 heightStyle 內容高度 : auto (自動, 預設), fill (與父元素同高), content (與內容同高)
 event 設定開啟標題之事件 (click 或 mouserover 等), 預設為 "click" (按滑鼠左鍵)


設定選項語法如下 : 

$("#accordion").accordion({option1: value1, option2: value2, .....})

除了透過選項物件設定屬性外, 也可以利用 option 方法存取 Accordion 物件的屬性, jQuery UI 物件方法的語法比較特殊, 第一參數是方法名稱, 第二參數是要操作的屬性, 備選的第三參數是要設定之屬性值, 如下所示 :

$("#accordion").accordion("option", 屬性名稱, 屬性值)       //settet : 設定選項屬性之值

如果沒有傳入屬性值則傳回該屬性之值 :

var value=$("#accordion").accordion("option", 屬性名稱)       //getter : 傳回選項屬性之值

網頁載入時折疊選單預設是開啟索引為 0 的第一個標題, 下面範例則是指定初始化時開啟索引為 1 的第二個標題 :


測試 5 : 初始化時開啟指定之標題 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2><a href="#">第 1 列標題</a></h2>
      <div>第 1 列內容</div>
      <h2><a href="#">第 2 列標題</a></h2>
      <div>第 2 列內容</div>
      <h2><a href="#">第 3 列標題</a></h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion({active: 1});
        });
    </script>
  </body>
</html>

效果如下 :




折疊選單預設只有一個標題會開啟其內容, 如果想關閉此功能, 讓其初始化時全部標題都收合, 則必須用 {active: false, collapsible: true} 去設定, 例如 :


測試 5-1 : 初始化時全部標題都收合 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2><a href="#">第 1 列標題</a></h2>
      <div>第 1 列內容</div>
      <h2><a href="#">第 2 列標題</a></h2>
      <div>第 2 列內容</div>
      <h2><a href="#">第 3 列標題</a></h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion({active: false, collapsible: true});
        });
    </script>
  </body>
</html>

結果如下 :




heightStyle 屬性預設為 "auto", 表示會依據內容自動調整高度, 但那是對內容本來就寫在 div 元素中的情況, 但若內容是網頁載入後用 Javascript 程式動態寫入 DOM 者來說就無效, 標題展開時不會完全打開內容容器, 解決辦法是將其設定為 "content", 表示是依據實際的內容調整容器高度 :

$("#accordion").accordion({heightStyle: "content"});

參考 :

https://stackoverflow.com/questions/6369241/jquery-accordion-height

例如 :


測試 6 : 設定內容高度依據實際資料自動調整 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2><a href="#">第 1 列標題</a></h2>
      <div>第 1 列內容</div>
      <h2><a href="#">第 2 列標題</a></h2>
      <div>第 2 列內容</div>
      <h2><a href="#">第 3 列標題</a></h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion();
        console.log($("#accordion").accordion("option", "heightStyle"));   //輸出 "auto"
        $("#accordion").accordion("option", "heightStyle", "content");
        console.log($("#accordion").accordion("option", "heightStyle"));   //輸出 "content"
        });
    </script>
  </body>
</html>

此處使用 option 方法先查詢 heightStyle 選項屬性之值, 可知預設為 auto, 然後再用 option 方法將其設定為 content. 這個設定的效果要動態地載入較長內容時才觀察得到.

折疊選單預設是用滑鼠點擊事件 (click) 來觸發標題之展開, 可以用 event 選項屬性來更改或添加所綁定之事件, 若有多個事件中間用空格隔開 :

$("#accordion").accordion({event: "mouseover"});

例如 :


測試 7 : 設定標題開啟收合之觸發事件 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2><a href="#">第 1 列標題</a></h2>
      <div>第 1 列內容</div>
      <h2><a href="#">第 2 列標題</a></h2>
      <div>第 2 列內容</div>
      <h2><a href="#">第 3 列標題</a></h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion({event: "mouseover"});
        });
    </script>
  </body>
</html>

可見只要滑鼠移到標題上面, 其內容就會展開了.

屬性選項 animate 可以控制標題展開收合時是否要套用動畫, 其值可以是布林值 true/false (預設 true); 也可以是毫秒為單位的整數, 下面範例是將 animate 設為 false 關掉動畫效果 :


測試 8 : 取消動畫效果 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2><a href="#">第 1 列標題</a></h2>
      <div>第 1 列內容</div>
      <h2><a href="#">第 2 列標題</a></h2>
      <div>第 2 列內容</div>
      <h2><a href="#">第 3 列標題</a></h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion({animate: false});
        });
    </script>
  </body>
</html>

可見展開與收合時的動畫效果消失了. 下面範例則是使用滑桿元件控制動畫毫秒數, 關於滑桿之操作參考 :

jQuery UI 學習筆記 (六) : 滑桿 (Slider)


測試 9 : 以滑桿控制動畫速度 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="hslider" style="width:300px;"></div><br>
    <div id="accordion">
      <h2><a href="#">第 1 列標題</a></h2>
      <div>第 1 列內容</div>
      <h2><a href="#">第 2 列標題</a></h2>
      <div>第 2 列內容</div>
      <h2><a href="#">第 3 列標題</a></h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion();
        $("#hslider").slider({
          min: 0,
          max: 500,
          create: function(e, ui) {
            var style={"width":"30px","text-align":"center"};
            $(this).find(".ui-slider-handle").css(style);
            },
          slide: function(e, ui) {
            $(this).find(".ui-slider-handle").html(ui.value);
            $("#accordion").accordion("option", "animate", ui.value);   //動態設定動畫毫秒數
            }
          });
        });
    </script>
  </body>
</html>




用搖桿調整動畫時間 (0~500 ms), 調到 0 時就沒動畫效果了.

雖然折疊選單與頁籤面板很類似, 但很可惜的是折疊選單沒有像頁籤面板那樣可用 Ajax 動態地從遠端載入內容的功能, 但可以透過 activate 事件來達成這個目的, 參考 :

How to load content in jQueryUI accordion dynamically

這個解決方案的原理就是利用 ui.newHeader 集合取得子元素 a 的 href 屬性, 然後透過 Ajax 功能取得遠端資源後呼叫 html() 將其寫進放內容的 div 元素容器裡, 所以網頁必須使用上面範例 4 的用 a 元素包覆標題的結構才行, 如下面範例所示 :


測試 11 : 利用 activate 事件實現 Ajax 動態更新內容 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <div id="accordion">
      <h2><a href="ajax1.htm">第 1 列標題</a></h2>
      <div>載入中 ...</div>
      <h2><a href="ajax2.htm">第 2 列標題</a></h2>
      <div>載入中 ...</div>
      <h2><a href="ajax3.php">第 3 列標題</a></h2>
      <div>載入中 ...</div>
    </div>
    <script>
      $(function(){
        $("#accordion").accordion({
          heightStyle: "content",
          activate: function (e, ui) {
            url=$(ui.newHeader[0]).children('a').attr('href');
            $.ajax({
                method: "GET",
                url: url,
                cache: false,
                success: function (data) {
                  $(ui.newHeader[0]).next().html(data);
                  }
              });
            }
          });
        });
    </script>
  </body>
</html>

此網頁中折疊選單的三個標題都用 a 元素連結遠端資源, 分別是 ajax1.htm, ajax2.htm, 與 ajax3.php, 前面兩個網頁內容都是簡單的 p 元素包覆的檔名, 分別是 :

<p>ajax1.htm</p> 與
<p>ajax2.htm</p>

為了測試動態內容, ajax3.php 會回應後端伺服器的當地時間 :

<?php
echo date("Y-m-d H:i:s");
?>

其次我將原參考網頁使用的 $.get() 改成 jQuery 底層函數 $.ajax(), 因為它有較多屬性可設定, 此處是為了取消瀏覽器的 cache 功能, 參考 :

Prevent browser caching of AJAX call result

關於 $.ajax() 用法參考 :

測試 jQuery 的 Ajax 函數 $.ajax()

結果如下 :






實際測試可知, 使用 activate 事件屬性確實能動態地從遠端取得資料後更新內容, 此方法彌補了 jQuery UI 未替 Accordion 內建 Ajax 功能之缺憾.

折疊選單的 API 並沒有提供動態新增一組面板 (標題+內容) 的方法, 但可以利用 jQuery 的 append() 方法以及 Accordion 元件的 refresh 方法來達成, 這是我在參考書目 5 (jQuery UI in Action) 這本書中找到的做法, 改編如下 :


測試 11 : 利用 append() 與 refresh 方法動態新增一組面板 [看原始碼]

<!DOCTYPE html>
<html>
  <head>
    <title></title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/hot-sneaks/jquery-ui.css" rel="stylesheet">
    <style>
      body {
        font-family: Arial, Helvetica, sans-serif;
        font-size:10px;
        }
    </style>
  </head>
  <body>
    <label for="new_title">新標題</label>
    <input type="text" id="new_title" value="新標題"><br>
    <label for="new_content">新內容</label>
    <input type="text" id="new_content" value="新內容">
    <button id="add">新增</button><br>
    <div id="accordion">
      <h2>第 1 列標題</h2>
      <div>第 1 列內容</div>
      <h2>第 2 列標題</h2>
      <div>第 2 列內容</div>
      <h2>第 3 列標題</h2>
      <div>第 3 列內容</div>
    </div>
    <script>
      $(function(){
        var accordion=$("#accordion").accordion();
        $("#add").button();
        $("#delete").button();
        $("#add").on("click", function(e){
          e.preventDefault();
          var new_item="<h2>" + $("#new_title").val() + "</h2>" +
                       "<div>" + $("#new_content").val() + "</div>";
          accordion.append(new_item).accordion("refresh");
          });
        });
    </script>
  </body>
</html>

此例將要新增的面板 (標題+內容) 傳給 append() 方法, 接著鏈式呼叫 refresh 方法更新摺疊選單外觀, 結果如下 :




輸入新面板的標題與內容後按新增鈕, 就會在底下新增一組面板了.

沒有留言:

張貼留言