2013年4月24日 星期三

[jstree] jstree 學習筆記

jstree 是一個好用的樹元件。基於 javascrpt 打造,適用於不同的瀏覽器。它被打包成 jquery 的插件,同時,他宣稱絕對免費。

樹元件在現在的視窗元件非常重要,你在檔案總管的左側一定會看到,沒有了它,要在各目錄間移動,將會非常不方便。

接下來,我把我學習如何使用這個元件的過程整理一下,就像是官方文件閱讀指南一般,而這過程,剛好就是由淺而深,由易而難的學習過程,希望可以幫助到有需要的人走一條比較平緩的學習之路。

在頁面引入

官網在文件的 core 頁有說明 http://www.jstree.com/documentation/core

其實,第一件事是下載 jstree 的檔案。在首頁http://www.jstree.com/的左上角有個綠色的下載按鈕,那裡打包的才符合文件所說。我曾經因為那個下載按鈕失效而去 github 下載,照文件來做發現是不能用的。原因我沒找出來,我想因為我不是 javascript 專家,所以沒辦法找出原因。

回到正題,在頁面引入,要先把檔案下載回來,現在是 jstree-v.pre1.0.zip。內容如下:

image

裡面的 jquery.jstree.js 就是了。

因為 jstree 寫成 jquery 的 plugin,所以第一個要引入的是 jquery。

<script type="text/javascript" src="_lib/jquery.js"></script>

若 jquery 你放在別的位置,就不用照抄。

接下來就是引入 jstree。有兩個版本,功能都一樣,只是一個 size 比較大,一個比較小。

<script type="text/javascript" src="jquery.jstree.js"></script>

其實任何位置都可以,只要你清楚改位置之後的影響。文件上有提到,檔名儘量不要改,因為像是 themes 插件的檔案路徑偵測會用到。如果非改不可,多數的插件有選項可以手動設定路徑。(也就是自動路徑偵測失效,只能自己手動設定各插件會用到路徑。)

很貼心的是,jstree 會需要的插件,若是有缺少引入,它會主動告知。

 

準備資料

在使用 jstree 之前要準備資料,才能讓 jstree 顯示。我一開始一頭霧水,不知道在哪裡有說明如何準備資料給jstree。於是在它的 demo 網頁挖,用 chrome 的開發者工具看。後來發現其實有說明,只是在不同的地方。

jstree 可以用的資料格式有三種。HTML、JSON、XML。要讀入資料時,要開啟不同的 jstree 插件。

HTML

官網文件在http://www.jstree.com/documentation/html_data

要使用 HTML 當資料來源,需要開啟 HTML_DATA 插件。

資料的格式如下:

<li>
    <a href="some_value_here">Node title</a>
    <!-- UL node only needed for children - omit if there are no children -->
    <ul>
        <!-- Children LI nodes here -->
    </ul>
</li>

jstree 會讀到資料後改變資料的結構。

<!-- one of the three classes will be applied depending on node structure -->
<li class="[ jstree-open | jstree-closed | jstree-leaf ]">
    <!-- an INS element is inserted -->
    <ins class="jstree-icon">&#160;</ins>
    <a href="some_value_here">
        <!-- another INS element is inserted -->
        <ins class="jstree-icon">&#160;</ins>
        Node title
    </a>
</li>

因此,底下是我做出來最簡單的範例,資料已經在網頁準備好的。

<html>

<script src="js/jquery-1.7.2.js"></script>
<script src="js/jquery.jstree.js"></script>

<script>
$(document).ready(function () {
    $("#demo1").jstree({
        "plugins" : [ "themes", "html_data" ]

    });
});
</script>
<body>
<div id="demo1" class="demo">
    <ul>
        <li>
            <a href="#">Root node 1</a>
            <ul>
                <li>
                    <a href="#">Child node 1</a>
                </li>
                <li>
                    <a href="#">Child node 2</a>
                </li>
            </ul>
        </li>
        <li>
            <a href="#">Root node 2</a>
        </li>
    </ul>
</div>
</body>
</html>

執行結果:(如果看起來少個目錄圖示,我就是這樣,因為拿別人的網頁來改的。那可能拿到舊的 css,一定要在下載下來的 zip 拿整包的 icon 及 css。)

image

這樣就已經是可以點擊張開及收合的功能。

若是要改用設定的方式呢?以下是範例。

<html>

<script src="js/jquery-1.7.2.js"></script>
<script src="js/jquery.jstree.js"></script>

<script>
$(document).ready(function () {
    $("#demo1").jstree({
        "core" : { "initially_open" : [ "root" ] },
        "plugins" : [ "themes", "html_data" ],
        "html_data" : {
            "data" : '<li id="root"><a href="#">Root node 1</a><ul><li><a href="#">Child node 1</a></li><li><a href="#">Child node 2</a></li></ul></li><li><a href="#">Root node 2</a></li>'
        }
    });
});
</script>
<body>
<div id="demo1" class="demo">
</div>
</body>
</html>

image

這範例是參考官方文件的,裡面順手交了一招核心功能的設定,預定打開的節點。

"core" : { "initially_open" : [ "root" ] }

該設定要傳入的是一個字串陣列,其內容是節點(<li>)的 id 值。要小心 id 設定時不能重覆,在一個網頁內要唯一。

使用 ajax 方式,我想等三種方式的基本都講完再說。因為他們有共通的奇妙之處。

json

官方文件在這http://www.jstree.com/documentation/json_data

要使用 json 當資料來源,要把 json_data 插件打開。基本的 json 資料結構要長成以下的樣子。

{
    "data" : "node_title",
    // omit `attr` if not needed; the `attr` object gets passed to the jQuery `attr` function
    "attr" : { "id" : "node_identificator", "some-other-attribute" : "attribute_value" },
    // `state` and `children` are only used for NON-leaf nodes
    "state" : "closed", // or "open", defaults to "closed"
    "children" : [ /* an array of child nodes objects */ ]
}

其中,data 是節點名稱,也就是 ui 上看到的名稱。attr 是給 jquery 的 attr 用的,若是有些東西要操作,id 很重要,那麼就必須在這裡設定,這裡的屬性會設定成 <li> 的屬性。state 是預設打開或關閉此結點。children 就是放子節點的位置。

<html>
<script src="js/jquery-1.7.2.js"></script>
<script src="js/jquery.jstree.js"></script>
<script>
$(document).ready(function () {
    $("#demo1").jstree({
        "core" : { "initially_open" : [ "root" ] },
        "plugins" : [ "themes", "json_data" ],
        "json_data" : {
            "data" : [
                {
                    "data":"Root node 1",
                    "attr":{"id":"node_1"},
                    "state":"open",
                    "children":[
                        {
                            "data":"Child node 1",
                            "attr":{"id":"node_2"},
                            "state":"open"
                        },
                        {
                            "data":{
                                "title":"Child node 2",
                                "attr":{"href":"node2.html"}
                            },
                            "attr":{"id":"node_3"},
                            "state":"open"
                        }
                    ]
                },
                {
                    "data":"Root node 1",
                    "attr":{"id":"node_4"},
                    "state":"closed",
                    "children":[]
                }
            ]
        }
    });
});
</script>
<body>
<div id="demo1" class="demo">
</div>
</body>
</html>

執行起來就跟之前的畫面一樣。

還記得,經過 jstree 處理過的節點的 html 樣子嗎?在 <li> 裡面有 <a>,若是要設定 <a> 的屬性,例如 href,那麼就要如同上面的 Child node 2 的設定方式。注意看 Child node 1 與 Child node 2 的不同,就在於 data 的值,一個是字串,一個是物件。物件裡面的 title 屬性就是節點的名稱,物件裡面的 attr 屬性就是 <a> 的 attr,物件裡面的 icon 屬性,若有 / 則會變成背景,不然就當 class 的值。

{
    "data":"Child node 1",
    "attr":{"id":"node_2"},
    "state":"open"
},
{
    "data":{
        "title":"Child node 2",
        "attr":{"href":"node2.html"}
    },
    "attr":{"id":"node_3"},
    "state":"open"
}

image

XML

與其他兩種方式相比,使用 XML,有兩種表示方式,一種是階層式。一種是平坦式,怎麼說呢?

它的結構如下:

<root>
    <item id="root_1" parent_id="0" state="closed">
        <content>
            <name><![CDATA[Node 1]]></name>
        </content>
    </item>
    <item id="node_2" parent_id="root_1">
        <content>
            <name><![CDATA[Node 2]]></name>
        </content>
    </item>
</root>

每個節點在 xml 裡是放在同一層,要組合成樹,就靠 parent_id 來連。這種方法對於資料是存放在資料表裡的人來說,是一大福音,代表自己可以少寫一段組合成樹的程式。

這裡的規則也很簡單,所有在 item 的屬性,都會轉到 <li> 的屬性。所有在 name 的屬性,都會轉到 <a> 的屬性。

為了避免使用到 xml 的保留字元,name 裡面的文字節點內容要用 CDATA 夾住。

 

厲害的 ajax 屬性

在三個資料來源的設定,ajax 的設定非常靈活。

http://www.jstree.com/documentation/html_data 裡面有個例子,「Using the ajax config option」。你如果去點開 Node 1,它底下長出 Node 1, Node2,再點開底下的 Node 1,又再長出兩個,越點越多…。

image image

ajax 裡面的 data 屬性若是設定成一個 function,可以在裡面獲得被點擊的節點,也就可以拿到被點擊點的 id。在這 function 回傳的資料,會被當成 ajax 的 data 項目回傳給 server。

我寫了個範例如下:

<html>
<script src="js/jquery-1.7.2.js"></script>
<script src="js/jquery.jstree.js"></script>
<script>
$(document).ready(function () {
    $("#demo1").jstree({
        "plugins" : [ "themes", "json_data" ],
        "json_data" : {
            "ajax":{
                "url":"ajax.html",
                "data":function(n){
                    return {id : n.attr ? n.attr("id") : 0 };
                }
            }
        }
    });
});
</script>
<body>
<div id="demo1" class="demo">
</div>
</body>
</html>

要準備一個 ajax.html 檔,其實裡面內容是 json 格式(反正 iis 不在乎)

[
    {
                    "data":"Root node 1",
                    "attr":{"id":"node_1"},
                    "state":"open",
                    "children":[
                        {
                            "data":"Child node 1",
                            "attr":{"id":"node_2"},
                            "state":"closed"
                        },
                        {
                            "data":{
                                "title":"Child node 2",
                                "attr":{"href":"node2.html"}
                            },
                            "attr":{"id":"node_3"},
                            "state":"closed"
                        }
                    ]
                },
                {
                    "data":"Root node 1",
                    "attr":{"id":"node_4"},
                    "state":"closed",
                    "children":[]
                }
]

依靠範例中的寫法,每次點開節點時,便會有 request 送到 server。data function 回傳的資料都當成 querystring 送出。

 

image

同時,ajax 裡面的 url 若是設定成 function,那它也可以在裡面得到被點擊的節點,同時,this 在那個 function 裡代表的是 jstree 的實例。在那裡面回傳的,會被當做是 url,於是就可動態產生 url,在採用 REST 開發的網站可以用 /get_children/node_2 之類的拿到資料。

以下是我的範例:

<html>
<script src="js/jquery-1.7.2.js"></script>
<script src="js/jquery.jstree.js"></script>
<script>
$(document).ready(function () {
    $("#demo1").jstree({
        "plugins" : [ "themes", "json_data" ],
        "json_data" : {
            "ajax":{
                "url":function(n){
                    var _id = n.attr ? n.attr("id"):0;
                    return _id + "/ajax.html"
                }
            }
        }
    });
});
</script>
<body>
<div id="demo1" class="demo">
</div>
</body>
</html>

image 

結論

在這裡先快速帶到可以看得見的例子,把引入,資料準備的各種方式寫清楚,就不會一直 try and error 都看不到東西 (就是說我自己)。同時,了解 ajax 屬性的用法,可以善用其特性提高效能及使用者經驗。

2013年4月9日 星期二

[android]在 sdcard 寫檔案

首先要 WRITE_EXTERNAL_STORAGE 的 permission。所以要到 AndroidManfest.xml 加入設定。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

再來就是範例,我希望有 BufferedWriter 可以增進效率,但是,目前我還沒寫好配套措施,所以現在看起來很笨。先將就著用…。

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import android.os.Environment;
import android.util.Log;

public class MyFile {
    private String _filename = "";
    private File _filehandle = null;
    private String TAG = this.getClass().getName();
//    private int writecount=0;
//    private int writeubound=200;

    public MyFile(String filename) {
        this._filename = filename;
    }

    public void write(String content) {
        if (this._filehandle == null) {
            File root = Environment.getExternalStorageDirectory();
            this._filehandle = new File(root, this._filename);
        }
        try {
            FileWriter filewriter = new FileWriter(this._filehandle, true);
            BufferedWriter out = new BufferedWriter(filewriter);
            out.write(content);
//            writecount +=1;
//            if(writecount>writeubound){
//               
//            }
            out.flush();
            out.close();
        } catch (IOException e) {
            Log.d(TAG, "write failed");
        }
    }
//    public void close(){
//        if (this._filehandle!=null){}
//    }
}

 

完畢。