2016年6月4日 星期六

用 jQuery 無痛讀檔、顯示

用 jQuery 的 get 讀本地檔案 我初學 javascript 時, 萬萬沒想到最困擾的竟會是 「讀檔」 這個單純/重要/基本的工作, 更沒想到讀本地的檔案竟然比讀遠方的檔案更困難! 現在摸出門路了, 快用 jquery 所提供的 $.get()) 跟 promise 機制寫一個 一次讀多個檔案的簡單範例 readfile-jq.js 跟 readfile.html, 幫你省下當初我不斷撞牆的辛酸路。 因為程式太短了, 就再加碼順便示範如何用 DataTables 跟 jqTree 把 .csv 跟 .json 兩類資料結構清楚地呈現在 html 頁面上, 以便 (訪客) 不必開 console 就能略微 (幫你) 除錯。

一、 從網址讀檔

先在 html 裡面引用 jquery:

<script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>

然後在程式碼裡面就可以用 $.get() 讀取網路上的某個檔案。 不過我們也很常需要一次讀完好幾個檔案再開始處理。 根據 這個問答, 採用 $.when().done() 就可以一次讀好幾個檔, 不需要繁雜的 nested callback:

    clothes = $.get(網址一);
    gift = $.get(網址二);
    $.when(clothes, gift).done(init);

那兩個網址是我其他程式的範例資料檔, 都是高雄鹽埕區的商家地理資訊。 一個是服飾店清單, .csv 格式; 另一個是禮品店清單, .geojson 格式。 傳回來的是一個 promise。 你可以搜尋 「promise 機制」 進一步研究; 不過我急著只想用 .when 跟 .done 把資料讀進來就好; 至於 promise 是什麼... 請允許我 「保證」 以後再找時間再研究吧 :-)

在 callback (也就是 init() ) 裡面, 所接收到的兩個參數就對應到當初傳入 when 的兩個參數。 每個參數是一個陣列, 第 0 個元素就是讀進來的檔案內容, 存成一個很長的字串。 所以你可以這樣確認讀檔成功:

function init(clothes, gift) {
    console.log('clothes: ', clothes[0], ' \ngift', gift[0]);
}

如果遠方的伺服器不讓你的 javascript 讀資料, 可能需要請站長開放 CORS

如果你的資料檔採用 json 格式儲存, jQuery 還提供一個更方便的 $.getJSON() 函數。 它會自動幫你把讀進來的一長條字串解析成 javascript 的資料結構。 也就是說, 這一段:

    gift = $.get(網址二);
    console.log(gift);   // 以一長條字串印出
    console.log(JSON.parse(gift)); // 以物件格式印出

跟這一段:

    gift = $.getJSON(網址二);
    console.log(JSON.stringify(gift)); // 以一長條字串印出
    console.log(gift);   // 以物件格式印出

效果相等。

二、 讀本地檔

或者, 檔案也不一定要放在雲端 -- $.get() 跟 $.getJSON() 也可以讀本機的檔案。 前提是: 你必須用網址的方式開啟網頁: http://localhost/...html 也就是說你需要自架 apache2 或 nginx。 如果你想用 「檔案=>開啟」 的方式開啟網頁, 也就是網址列顯示 file:///...html 那就比較麻煩了。

如果你的瀏覽器是 chromium, 那麼啟動時必須在命令列上這樣下才可以讀本地檔: chromium-browser --allow-file-access-from-files。 詳見 chrome 的禁讀令

如果你的瀏覽器是 firefox, 那麼你必須 關閉 strict_origin_policy, 而且只能用 jQuery.getJSON。 在這個情況下, jQuery 的 .get .when .done 的 callback 都無效。 但是 .get() 還是會去讀檔案, 所以只要加上 window.setTimeout() 等待一會兒, 資料還是會出現。 那麼, 「使用者到底是用 http:// 還是用 file:/// 開啟你的頁面?」 這在你的程式裡該如何測試呢? 可以用 if (window.location.protocol == 'file:') { ... } 詳見 這個問答

總之, 先前我換了很多搜尋關鍵詞, 但是很奇怪地, 新手詢問讀取本地檔案的問題 總是得不到簡單清楚的答覆。 更別提想要一次讀多個檔案, 還必須用頭昏腦漲的巢狀 callbacks。 如果不是 jQuery 或 d3 這類函式庫出面解救, 我對 javascript 早就棄鍵投降了。 為了表達抗議, 我開了一個 github 專案 :-) javascriptCanReadLocalFiles , 你可以到那裡抄範例程式碼。

只想要讀檔案的同學, 到這裡就可以下課了 :-) 但是如果想把資料印出來呢? 開發者自己當然可以用 console.log() 來檢視; 但若想把大量資料印給網頁訪客看呢? 其他程式語言沒有這個區別; javascript 就比較辛苦一點了。 還好有 jQuery 跟很多的 jQuery 外掛。

三、 用 DataTables 在網頁上顯示 (例如從 .csv 檔讀進來的) 表格

用 DataTables 在網頁上顯示
(例如從 .csv 檔讀進來的) 表格 &
用 jqTree 在網頁上顯示樹狀資料結構 (例如 json)DataTables 外掛可以讓你的 html table 變得更清楚漂亮且具互動性, 很適合拿來呈現二維表格類型的資料, 例如 .csv 檔讀進來的東西。 在 html 裡面除了要引用 css 跟程式碼:

<link type="text/css" rel= "stylesheet" href="https://cdn.datatables.net/1.10.12/css/jquery.dataTables.min.css"/>
<script src="https://cdn.datatables.net/1.10.12/js/jquery.dataTables.min.js"></script>

還要預留一個 table:

<table id="table_display" class="display">
</table>

然後在程式裡呼叫 .DataTable() 建立表格:

$('#table_display').DataTable({
    ordering: false, // 國名跟都市名稱沒什麼好排序的啊!
    columns: [
 { title: '國家' }, { title: '首都' }
    ],
    data: [
 ['臺灣', '臺北'], 
 ['越南', '河內'],
 ['印尼', '雅加達']
    ] 
});

以我們讀入的 .csv 資料而言, 要先做以下前置處理:

  1. .split('\n') 把資料拆成一列一列。
  2. .shift() 抓出第一列當表頭 (整修一下當做 columns 欄)
  3. .forEach().split(',') 把剩下的內容拆成一個二維陣列 (當做 data 欄)。

DataTable 提供的使用者互動功能包含大量資料分頁顯示、 排序、 搜尋等等。 也提供多種佈景主題。 更進一步學習時, 推薦的閱讀順序: 從 Examples index 著手最簡單。 先想一下你的資料表格來源到底是靜態網頁、 ajax 遠端讀取、 javascript 程式碼、 還是來自本身伺服器端? 從 「Data sources」 那邊挑一個符合你需求的範例來抄。 再看看這個頁面的其他範例有沒有你需要學的功能。

四、 用 jqTree 在網頁上顯示樹狀資料結構 (例如 json)

jqTree 外掛可以拿來呈現樹狀的資料結構, 例如從 .json 檔讀進來的東西。 在 html 裡面除了要引用 css 跟程式碼:

<link type="text/css" rel= "stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqtree/1.3.3/jqtree.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqtree/1.3.3/tree.jquery.min.js"></script>

還要預留一個 div:

<div id="tree_display"> </table>

然後在程式裡呼叫 .tree() 建立表格:

$('#tree_display').tree({
    data: ['root']
});

建議先剪貼 jqTree 首頁的範例來測試。 基本上就是傳一個陣列給 data 欄, 裡面每個元素是一個 hash, 有 name 跟 children 兩欄。 children 裡面又是另一層的陣列...

我寫的 json2jqTree() 函數可以把一個 json 結構轉成 jqTree 所需要的格式。 這個函數很適合拿來當做遞迴練習作業; 不會遞迴的同學直接剪貼帶走, 搭配 jqTree 也很實用。

jqTree 畫出來的樹狀結構可以收縮/展開, 讓訪客可以自行決定如何善用螢幕空間。

2 則留言:

  1. 加了一節 「讀本地檔」, 還開了一個 github 專案表達抗議 :-)

    回覆刪除
  2. 太感謝了
    callback 真的是弄不好
    希望以後能搞懂

    回覆刪除

因為垃圾留言太多,現在改為審核後才發佈,請耐心等候一兩天。