:::
針對查詢「jquery」依關聯性排序顯示文章。依日期排序 顯示所有文章

jQuery動態載入CKEditor注意事項

jQuery動態載入CKEditor注意事項

image

最近在寫標註工具的API,完成之前沒有好好做的載入器(loader),目標是要讓人用一行程式碼就能載入全部需要的工具。

在我做的KALS標註工具當中使用到了CKEditor,而要讓他與jQuery搭配,則需要額外載入一個調配器(adapters)。但是傳統的用法都是在<head>寫<script>標籤,這跟我要的動態載入不太一樣。

在四處碰壁嘗試之後,終於找到了正確的作法。在此做個筆記。


不可用的方法

由於我是要在網頁讀取完之後才開始動態載入,因此不能用document.write,否則會洗掉整個網頁。

原本我也想利用DOM建立<script>標籤,內含src屬性載入CKEditor,插入<head>當中,但卻發現這個插入的<script>標籤並不會執行,簡單來說就是沒有效果。

後來改用jQuery的getScript()方法,就能夠確實地讀取並執行CKEditor。不過仍有其他細節需要注意,以下說明。

正確的方法

正確的作法是:

  1. 建立<script src=”[CKEditor的網址]”></script>貼到<head>之中,供CKEditor本身去搜尋連結,自動載入其他檔案。
  2. 以jQuery.getScript()載入並執行CKEditor。
  3. 先載入CKEditor主程式,再載入調配器。

以下是簡單的範例程式碼:

//在這之前,你已經確認載入了jQuery

//路徑宣告
var _ckeditor_url = "/ckeditor/ckeditor.js";
var _adapters_url = "/ckeditor/adapters/jquery.js";

//載入函式
var _load_lib = function (_url, _callback) {
var _script_tag = $('<script type="text/javascript" src="' + _url + '"></script>');
_script_tag.appendTo($('head'));

$.getScript(_url, function () {
if (typeof(_callback) == 'function')
_callback();
});
};

//開始載入
_load_lib(_ckeditor_url, function () {
_load_lib(_adapters_url, function () {

//你接下來要做的其他程式

});
});

這是截自KALS載入器的部分程式,並沒有經過測試。有任何錯誤,請提醒我再檢查、修正。

(more...)

Blogger 圖片延緩載入功能 (image lazy load)

Blogger 圖片延緩載入功能 (image lazy load)

image image

繼前一篇無限捲頁功能,這一篇實作了Blogger說要做但是尚未實裝的圖片延緩載入功能。我先簡單地介紹一下圖片延緩載入的原理,然後再講述要如何安裝。


圖片延緩載入

當網頁開啟之後,瀏覽器會嘗試讀取網頁中的所有圖片。如果電腦效能不足、網頁圖片數量過多,剛開啟網頁的時候,瀏覽器就會像是當機一樣無法動彈,甚至連拉個捲軸看看其他地方都不行。

為了解決網頁圖片數量過多的問題,之前我提到的CSS Sprites是一個解決方案,它嘗試將許多零星的圖片合併成一張,以節省讀取圖片需要的額外載入時間(overhead)。

image

另一個更直接的作法,就是這篇要談的圖片延緩載入功能。當網頁內容相當多的時候,使用者並不會一口氣就閱讀所有的內容,而只會瀏覽到「視窗所看到」的範圍。那麼在這個範圍之外的圖片,就可以暫時先不載入,而可以用像是上圖一樣的灰色背景來替代。那個灰色並不是圖片本身,只是一個佔位圖片(placeholder)而已。

圖片延緩載入功能會分析網頁指定範圍中的所有圖片,先將使用者瀏覽範圍之外圖片全部替換成佔位圖片,停止網頁進行實際上的載入動作。

image

當網頁瀏覽到該圖片位置的附近時,圖片延緩載入功能會偵測到這個事件發生,並將佔位圖片替換成真正的圖片,這時才即時進行載入。也許使用者在瀏覽時會覺得怎麼突然會卡一下,不過那個瞬間也是相當地短。如果每一張要讀取的圖片也不大的話,圖片延緩載入功能並不太會影響使用者瀏覽的節奏。

題外話:圖片預先載入功能

提到圖片延緩載入功能,就會讓人想起另一種相反的作法:圖片預先載入功能。

圖片預先載入是先在使用者看不到的地方載入圖片,讓圖片存入瀏覽器的快取。當使用者真的要瀏覽該圖片的時候,從快取中載入的圖片會讓瀏覽更為流暢。

實作時,通常是在讀取這網頁的前一個網頁中實作。例如有些網站會在真正的首頁前放一個Flash影片播放的網頁,那麼當使用者欣賞影片的時候,JavaScript或其他程式就在背後偷偷地載入真正首頁中的圖片。那麼當使用者看完影片、進入真正的首頁時,首頁中的圖片就能夠快速地顯示出來。

圖片預先載入功能的重點在於「猜得到使用者接下來要看的圖片」。在導覽功能四通八達的現代網站,我們難以預料使用者的下一頁究竟會看是什麼資料、到底要預先載入什麼圖片。而早期網站設計都喜歡的假首頁(就像上面說的,用來擺放影片之類的首頁),現在也逐漸為人捨棄不用。就算是一踏入假首頁,使用者也多會略過而直接進入真正的首頁,而沒有機會用到圖片預先載入的功能。

另一個使用圖片預先載入功能的時機是「停留替換圖片」(hover)。當滑鼠移到某張圖片或某個位置時,將該位置顯示的圖片替換成其他圖片。許多網頁都會使用這一技巧,它也很容易讓人猜到使用者接下來要讀取的下一張圖片是什麼,因此特別適合用於圖片預先載入。圖片延緩載入Image Lazy Load Plugin for jQuery的作者Mika Tuupola也有針對圖片預先載入功能提出一個方法,詳情請看Preload Images Sequentially With jQuery這篇文章。


安裝圖片延緩載入

安裝小工具

跟大多數Blogger額外設定的功能一樣,圖片延緩載入功能也是從新增HTML/JavaScript小工具開始。

2011-06-25_233326 設計 網頁元素

進入Blogger的管理介面,進入「設計」的「網頁元素」,在頁尾的地方新增小工具。

2011-06-25_233246 新增JavaScript

選擇其中的HTML/JavaScript。

image

標題留空,內容填入以下圖片延緩載入程式碼:

<script src='http://www.google.com/jsapi' type='text/javascript'></script>
<script type='text/javascript'>google.load("jquery","1.2.6");</script>
<script src='http://sites.google.com/site/puddingchen35/2011-06-blogger-image-lazy-load/jquery.lazyload.mini.js' type='text/javascript'></script>
<script type="text/javascript">
$("#main .blog-posts .post-body img").lazyload({
effect : "fadeIn",
placeholder: "https://sites.google.com/site/puddingchen35/2011-06-blogger-image-lazy-load/grey.gif"
});
</script>

儲存設定,這樣就完成了。

設定圖片延遲載入

在圖片延遲載入的程式碼中,有兩個地方可以設定,位於以下範圍中:

$("#main .blog-posts .post-body img").lazyload({
effect : "fadeIn",
placeholder: "https://sites.google.com/site/puddingchen35/2011-06-blogger-image-lazy-load/grey.gif"

});

  1. effect:顯示真正圖片時的特效。預設值是「show」,表示直接替換;「faceIn」是淡入效果。
  2. placeholder:佔位圖片。不使用佔位圖片的話,圖片甚至連保留位置都沒有,而讀取時就會像是突然插入一張圖片、即時改變頁面的排版,我覺得不是很好看。在此採用原作者建議的灰色底圖作為佔位圖片,你也可以自行改為其他佔位圖片。

其他還有很多設定,詳情請看Lazy Load Plugin for jQuery的說明。

製作參考

我在建構此功能時,一開始是先從別人的Word Press網站上看到的jQuery Image Lazy Load外掛。而這個外掛其實又是源自於jQuery的Lazy Load插件。製作者Mika Tuupola將圖片延緩載入功能寫得相當完整,因此我也沒有多做修改,以下只是額外撰寫了供Blogger使用的語法而已。


結語

image

圖片延緩載入功能安裝之後,基本上就可以放著正常運作。可是我用一用赫然發現,偶爾還是會出現像上圖一樣的讀取失敗畫面。我在想這比較可能是Chrome功能的問題,而不是圖片延緩載入功能的問題吧。

其實圖片延緩載入比前一篇的無限捲頁還要早做,而且比無限捲頁還簡單很多,但因為怕無限捲頁做到最後自己都忘了,所以這一篇才延緩到現在才寫吧。

(more...)

JSONP跨網域傳送檔案:以POST方法實作

JSONP跨網域傳送檔案:以POST方法實作

image

網頁應用設計中的JSONP技巧可以做到跨網域資料傳送的功能,但它卻只能傳送純文字資料,而我希望能在跨網域的環境下開發AJAX上傳檔案的功能,那麼就需要一些小技巧才能進行。

雖然我想應該也有人提出這個作法了,不過實際上在之前我並沒有找到過。因此我把這個在論文中實作的技巧在此記錄、分享。

這篇用到大量的網頁設計專有名詞,主要是以AJAX設計網頁應用的人為主要對象撰寫的。


範例

image

這個範例網頁中,你可以上傳一個檔案,並填寫下面的敘述Description,再按下UPLOAD按鈕上傳。伺服器會回傳給你檔案的上傳結果、檔案名稱、檔案大小。檔案上傳最大是1MB,超過1MB的檔案,伺服器會回傳錯誤結果。

原本我是將HTML網頁放在Dropbox空間(http://dl.dropbox.com/),而伺服器則是放在You Hosting的空間(http://pulipuli.co.cc/),想用兩個不同網域來呈現透過POST方式實作JSONP跨網域檔案上傳的範例。但是因為You Hosting放著一陣子就被強制關閉了,所以很遺憾的,在此無法看到線上即時的成果。(2011/9/19更新)

此範例中用了三個檔案,請直接下載研究、並自行配置使用吧:

應用背景

使用AJAX開發網頁應用的程式設計師,大多時候都是在同個網域之下處理client(使用者端電腦)與server(伺服器)之間的訊息傳遞。更進一步的,利用jQuery框架,以JSON物件、GET方式傳遞資料的jQuery.getJSON()函數,讓處理AJAX資料傳遞更為簡單。

但是我的論文中需要使用的AJAX跟上述常見情況有個很大的差別。第一個是跨網域,client端呼叫server端資料,這兩者是位於不同的網域,因此普通的GET或POST方式都無法讓client端取得server端的資料,必須仰賴JSONP(JavaScript Object Notation with Padding)技巧;其二,這次要傳送的資料並非純文字或可以用JSON來組織的資料,而是需要上傳二進位的檔案,或是超過GET資料傳送上限的資料量。在這種情況下是不能單純使用JSONP,必須使用POST方式與form表單傳送資料才行。

因此,我將POST的傳送優勢與JSONP的跨網域AJAX特色融合,寫成以POST方式實作的JSONP跨網域檔案傳送。

角色

在此應用中是以前端的瀏覽器與遠端的伺服器進行溝通合作,因此我先簡單敘述這兩種角色。

「C」 client

在這篇文章中,我以「C」表示「client」(客戶端)瀏覽器中執行的程式語言,主要是HTML跟JavaScript,這也是AJAX的基礎。多虧了jQuery框架克服了相容性問題,因此在Chorme、Firefox或IE都可以使用。

在此範例中,「C」是擺在Dropbox的空間,他並不具備伺服器端的功能,不能判斷檔案大小。此外,我將「C」的HTML外觀寫在client.html當中,而JavaScript的邏輯運作寫在client.js當中。稍候會再敘述他們的功用。

「S」 server

另外「S」代表「server」(伺服器),在此範例中我以PHP寫成,擺在You Hosting空間。伺服器端的程式語言具備了處理檔案的能力,可以判斷檔案大小。在此利用AJAX架構,讓「C」能夠與「S」進行溝通。

為了利用POST傳送檔案,並能以JSONP進行跨網域溝通,「S」必須判斷請求方式(request method)是POST還是GET。並以session來暫存狀態。

稍微有點概念之後,接下來就是實際看看這方法要怎麼運作了。


Step1. C1:資料準備

 image

為了讓大家好理解,此範例中的檔案上傳做得跟傳統網頁一樣。這是一個<form>表單,裡面有個可以選擇檔案上傳的input,並限制只能上傳1MB大小的檔案,還有一個可以撰寫「Description」(敘述)的input,最後則是一個submit(遞交)的「UPLOAD」按鈕。

在這個表單前面,引用了大家的好朋友jQuery以及我另外寫的client.js;表單後面則有個<div>容器,負責顯示待會跟伺服器溝通的結果。

原始碼很簡單,寫在client.html中,摘錄如下:

<script src='http://www.google.com/jsapi' type='text/javascript'></script>
<script type='text/javascript'>google.load("jquery","1.2.6");</script>
<script src="client.js" type="text/javascript"></script>
<form id="form" method="post" action="http://pulipuli.co.cc/20110517-jsonp-post/server.php" enctype="multipart/form-data">
    <input type="hidden" name="max_file_size" value="1048576">
    <label>File: <input type="file" name="file" /> * Max File Size: 1MB</label>
    <br />
    <label>Description: <input type="text" name="description" value="The description of this file."></label>
    <br />    
    <button type="submit">UPLOAD</button>
</form>
<div id="output"></div>

另外,為了讓這個form表單能具備AJAX跨網域上傳的功能,我利用JavaScript幫他做了些初始化的調整,讓form的遞交會以AJAX方式進行,並設定回傳資料時所要執行的回呼函數(callback)。

這部份寫在client.js開頭,程式碼如下:

/**
 * Initialize Form
 */
$(function () {
    
    $("#form").submit(function () {
        
        if ($(this).attr("jsonp_by_post") == null)
        {
            $.jsonp_by_post(this, function (_result) {
                //...Step7時再講解... 
}); return false; } }); });

Step2. C2:AJAX式的POST遞交

當form的UPLOAD按鈕被按下時,就會開始一連串的AJAX式的POST遞交作業。

在此步驟中,JavaScript會做以下事情:

  1. 以timestamp作為辨識每次檔案上傳作業的代號。
  2. 讀取form的資料,並將timestamp設定到action的網址中,提供伺服器辨識每次檔案上傳作業的代號。
  3. 建立一個接收資料的iframe,隱藏之,並設定name。
  4. 調整form的action跟name,讓form的遞交會傳送到iframe去,而不是把整個網頁都替換掉。
  5. 給form做個記號(此範例用jsonp_by_post屬性),以免跟Step1初始化form的submit事件相衝突。
  6. 設定iframe遞交完成之後的後續動作。
  7. form執行遞交作業。

程式碼寫在client.js,摘錄如下,其中6.設定iframe遞交完成之後的動作,我會在下面的Step4詳細敘述,這邊先以略過。

$.jsonp_by_post = function (_form_ele, _callback) {
    
    var _timestamp = (new Date()).getTime();
    
    var _form_obj = $(_form_ele);
    
    var _action = _form_obj.attr("action");
    var _action_post = _action + "?timestamp=" + _timestamp;
    
    //建立接收資料的iframe
    var _iframe_name = _action + _timestamp;
    
    var _iframe_obj = $('<iframe></iframe>')
        .attr('name', _iframe_name)
        .appendTo($('body'));
        
    //隱藏iframe
    _iframe_obj.css('width', '0').css('height', '0')
        .css('position', 'absolute').css('left', '-1000px').css('top', '-1000px');
    
    //調整傳送資料的form
    _form_obj.attr('action', _action_post)
        .attr('target', _iframe_name)
        .attr('method', 'post')
        .attr('enctype', 'multipart/form-data');
    
    //防止重複觸發初始化的事件
    _form_obj.attr('jsonp_by_post', 'true');
        
    //設定iframe讀取完畢之後的動作
    
    _iframe_obj.load(function () {
        
        //...Step4時再講解...
        
    });
    
    //執行遞交
    _form_obj.submit();
};

Step3. S1:伺服器接收POST資料

伺服器端的程式會依照請求方式的不同,決定此程式是要接收檔案上傳,還是要回報檔案上傳的結果。

在Step3中,form以POST方式呼叫了位於另一個伺服器的server.php,並執行以下動作:

  1. 接收$timestamp,作為判斷這次作業的辨識代號,並與一個特定的$header組成session使用的$index。
  2. 透過透過PHP的$_SERVER['REQUEST_METHOD']來判斷請求方式。
  3. 如果是POST,則將檔案的檔名、大小、以及從POST過來的description資料儲存在session當中。
  4. 判斷檔案上傳的狀態,失敗就是「error」,成功則是「sussesful」。
  5. 顯示一些資料,作為簡單偵錯。但其實可以省略。

以下程式碼寫在server.php中,摘錄如下。請求方式是GET的情況,我會在下面的Step6再來講解。

<?php
$header = "data";
$timestamp = $_GET["timestamp"];
$index = $header . $timestamp;

session_start();

if ($_SERVER['REQUEST_METHOD'] == "POST")
{
    $description = $_POST["description"];
    
    $_SESSION[$index]['name'] = $_FILES['file']['name'];
    $_SESSION[$index]['size'] = $_FILES['file']['size'];
    $_SESSION[$index]['description'] = $description;
    
    if ($_FILES['file']['size'] == 0)
    {
        $_SESSION[$index]['state'] = 'error';
        echo "false";
    }
    else
    {
        $_SESSION[$index]['state'] = "sussesful";
        echo "true";
    }
}
else
{
    // ...Step5時再講解...
}

Step4. C3:以JSONP取得檔案上傳結果

當iframe讀取完畢之後,「C」就可以知道檔案上傳動作已經結束。但是因為POST在跨網域的狀況下是無法取得iframe裡面的資料,所以必須要改用JSONP的方式來跟「S」取得結果。

這些動作寫在Step2中省略的iframe onload事件中,大概動作如下:

  1. 設定JSONP的網址,加入jQuery.getJSON()特有的callback=?參數。
  2. 執行jQuery.getJSON(),並設定執行完後的回呼函數。

這些程式碼寫在client.js中,摘錄如下。執行完getJSON()的回呼函數會在Step6講解。

    //設定iframe讀取完畢之後的動作
    
    _iframe_obj.load(function () {
        
        //設定get方法JSONP的網址,要注意加入了JSONP特有的callback參數
        var _action_get = _action + "?timestamp=" + _timestamp 
            + "&callback=?";
          
        $.getJSON(_action_get, function (_result) {
            // ...Step6時再講解...
        });
        
    });

Step5. S2:伺服器回傳檔案上傳的結果

以JSONP方式呼叫伺服器時,「S」是以GET方式運作,他會做以下動作:

  1. 接收$timestamp,並與$header組成$index。這跟Step3做的事情一樣。
  2. 判斷請求方法為GET。
  3. 取得回呼函數的代號$callback,這是搭配jQuery.getJSON的作法。
  4. 從$index取得session資料,將檔案上傳結果存進$output字串。
  5. 刪除session資料,避免後來有心人再利用同樣的timestamp取得資料。
  6. 以JSONP方式輸出。

以下程式碼寫在server.php中,摘錄如下:

<?php
$header = "data";
$timestamp = $_GET["timestamp"];
$index = $header . $timestamp;

session_start();

if ($_SERVER['REQUEST_METHOD'] == "POST")
{
    // ...Step3講解...
}
else
{
    $callback = $_GET['callback'];

    $output = '';
    if (isset($_SESSION[$index]))
    {
        $output = $_SESSION[$index]['state'].'; ' 
            .$_SESSION[$index]['name'].'; '
            .$_SESSION[$index]['size'].'; '
            .$_SESSION[$index]['description'];
            
        unset($_SESSION[$index]);
    }

    if (is_null($callback))
        echo $output;
    else
        echo $callback."('".$output."');";    
    
}


Step6. C4:接收資料,復原form,呼叫回呼函數

以jQuery的getJSON取得資料之後,接下來就是復原form、刪除臨時建立的iframe,然後呼叫回呼函數。

這段程式碼也很簡單,寫在client.js中,摘錄如下:

        $.getJSON(_action_get, function (_result) {
            
            //復原form
            _form_obj.attr('action', _action)
                .removeAttr('target')
                .removeAttr('jsonp_by_post');
            
            //移除iframe
            _iframe_obj.remove();
            
            _callback(_result);
        });

Step7. C5:結果輸出

在Step1初始化時設定好的回乎函數會在最後接收資料,然後將結果輸出到div容器中。程式碼寫在client.js的上方,摘錄如下:

            $.jsonp_by_post(this, function (_result) {
                $("#output").html("Message: " + _result);
            });

如果檔案正常上傳,你會看到以下畫面:

image

如果你上傳超過1MB的檔案,則伺服器會記錄錯誤狀態,於是會看到以下畫面:

image

這樣就大功告成啦!


變化與應用

範例中的講解很簡單,身為能夠舉一反三的程式設計師,腦袋裡面應該已經呈現出很多不同的應用方式吧。這邊我大略提一下幾個變化的重點,還有最後應該注意的安全性問題。

資料的輸入

在此範例中,我所有資料都是來自於撰寫好的form表單。實作時,我們的原始資料不一定是真的來自form表單,通常會以JSON、陣列、變數形式存在記憶體中,以方便JavaScript進行處理。

如果是以JSON等非form的資料型態,想要進行跨網域的AJAX傳輸,那麼你還是得要自己將這些資料寫成form,這樣才能以POST進行遞交。雖然多了點步驟,但相信對於熟悉jQuery的你來說應該不是什麼問題。

安全性問題

網頁之間有很多跨網域的限制,而本篇旨在於打破這個跨網域限制來做到更多功能,但相對的就會負擔起更多安全性問題。

提供JSONP的服務,很容易就會受到跨網域指令碼(Cross-site Script)的攻擊,而被找漏洞、挖出使用者的資料。我並不是資安專家,對這方面研究還不夠深入,只能提供幾個簡單的安全性加強方向。大致上,我建議搭配身分認證的session、來源IP或網址,來判斷這個C是否有跟S溝通的資格,S再決定是否接受C的POST或GET請求。這樣應該會安全許多吧。

範例程式中為了講解方便,所以並沒有加上這些措施。如果你要將這個方法放到自己的應用程式中,請務必做好安全性的防範措施。


結語

這是我論文系統中實作的一個技巧,但因為論文無法寫出這些細節,所以我以寫blog的方式,記錄這個技巧的細節,供有需要的人參考。實際上這篇從想寫、擬完心智圖、寫了一半覺得不滿意、又改了好多次,到現在才完成。

這只是一個很基礎的AJAX技巧,可以應用到很多地方,可以進一步寫成更漂亮的framework或library。也許已經有人發表過這種方法了也說不定,不過,我另一個目的也只是給未來的自己留下一個參考而已,就作為一篇記錄吧。

(more...)

解決動態載入CSS失敗的問題:不要修改<link>標籤的屬性 / Why Loading CSS Dynamically Fail: Don't Modify <link> While Loading

解決動態載入CSS失敗的問題:不要修改<link>標籤的屬性 / Why Loading CSS Dynamically Fail: Don't Modify <link> While Loading

image

我在KALS專案中有用到以jQuery動態載入CSS的技巧,但是最近在Chrome上卻意外不能執行。研究之後才發現這是因為我在載入CSS途中去修改CSS載入標籤的title屬性,造成CSS載入失敗。解決方法是應該在建立載入CSS的標籤時就加入title屬性,而不要事後修改。

(more...)

如何閱讀JavaScript/CSS壓縮程式碼?快使用程式碼格式化工具 / How to Maxify/Parsing/Format Minified Code

如何閱讀JavaScript/CSS壓縮程式碼?快使用程式碼格式化工具 / How to Maxify/Parsing/Format Minified Code

image

為了閱讀被壓縮(minify)的JavaScript與CSS程式碼,我們可以使用線上工具Code Formatter或是NetBeans的format指令來為程式碼進行排版。

You can use some tools to maxify/parse/format the minified JavaScript or CSS codes, for example, Online Code Formatter or NetBeans.


關於壓縮程式碼 / Minify Code

JavaScript與CSS程式碼的特性在於必須下載到使用者客戶端才能執行,因此降低程式碼的大小是很常見的方式。JavaScript跟CSS通常是以壓縮(minify)的形態發佈給大家使用。被壓縮的程式碼檔案名稱通常會加上「.min」,而原始碼通常會加上「.src」,舉例來說:

image

這是jQuery 1.11.1原始碼的樣子。檔案大小是276KB。

image

這是jQuery 1.11.1壓縮程式碼的樣子。檔案大小是93.5KB,幾乎是原始碼的1/3大小。

壓縮程式碼中會移除註解、刪除空白與換行、縮短變數名稱(例如變數用了var book,會縮短成var a)。我之前也介紹過使用YUI CompressorMinify壓縮程式碼的方法。而現在壓縮JavaScript跟CSS已經是主流用法,像是Fat-Free Framework中也直接內建了壓縮工具,非常方便。

為什麼要閱讀被壓縮的程式碼? / To Read Minified Code

雖然像是jQuery這種知名的函式庫會提供非壓縮的原始碼供大家閱讀研究,但是還是很多專案並沒有提供解壓縮的原始碼,而只有被壓縮的程式碼可以看。

我最近在研究如何撰寫CKeditor的plugin,可惜他的教學跟文件寫的並不是很好。後來我覺得比起研究文件,不如直接修改程式碼,說不定還比較快。

2014-09-05_164627

可惜的是,大部分CKeditor中plugin的程式碼都是壓縮後的結果,閱讀起來不太容易,我們需要一些解壓縮(maxify)工具來輔助我們閱讀這些程式碼。

在此介紹兩種格式化被壓縮程式碼的方便工具:NetBeans的format指令跟線上工具


NetBeans的格式化指令 / “Format” in NetBeans

我使用的是NetBeansIDE 8.0。要格式化被壓縮的JavaScript程式碼的做法如下:

  1. 開啟JavaScript檔案
    2014-09-05_170730
  2. 開啟Source > Format 
    2014-09-05_170801
  3. 變成了漂亮的排版
    2014-09-05_170855

不過你可以注意到下面還是有一團程式碼沒有解壓縮到,這似乎是NetBeans的限制。此外,NetBeans也不能解壓縮CSS程式碼,使用範圍有限。

使用Code Formatter解壓縮 / Maxify Code on Online Code Formatter

這個工具名稱叫做「Format, Beautify, Maxify, Unpack or Deobfuscate JavaScript/jQuery/HTML/JSON/CSS Codes」,有點長,還是用網址上的Code Formatter來稱呼好了。

image

這工具用法很簡單。首先先把壓縮的程式碼貼在框框中,然後按下下面的按鈕「Click here to Format/Beautify/Maxify Your JavaScript/jQuery/JSON//HTML Codes」。

image

程式碼就以漂亮的版面排版好了,而且NetBeans沒解開的後面部分也排版的漂漂亮亮,這樣就能夠更輕易地閱讀程式碼了,真是令人開心呢。

希望這些工具能夠幫助程式開發者更容易閱讀程式碼。學程式的第一步就是模仿別人怎麼寫,加油!

(more...)

DSpace擴增MediaFilter格式(安裝篇)

布丁布丁吃布丁

DSpace擴增MediaFilter格式(安裝篇)

DSpace擴增MediaFilter的目錄:

本篇介紹DSpace擴增MediaFilter格式的安裝篇,安裝完之後,還要做一次[dspace]/bin/filter-media才能有展示篇的功能。

需求環境

  • DSpace 1.5.0 release (1.4.2或之後的版本似乎也可以)
  • 作業系統:CentOS 5 Final
  • 必須要具備yum的功能
  • 必須安裝OpenOffice 2.0以上

用yum安裝轉檔軟體

如果你不是root的話,先切換到root權限

[dspace@dspace ~]# su root

修改/etc/yum.repos.d/CentOS-Base.repo

[root@dspace ~]# vim /etc/yum.repos.d/CentOS-Base.repo

你可以上鳥哥來看一下vim文字編輯器要怎麼使用。

在該檔案最後加入以下設定:

[dag]
name=Dag RPM Repository for Red Hat Enterprise Linux
baseurl=http://apt.sw.be/redhat/el$releasever/en/$basearch/dag
gpgcheck=1
enabled=1

把金鑰加入RPM的資料庫

[root@dspace ~]# rpm --import http://dag.wieers.com/packages/RPM-GPG-KEY.dag.txt


如果你沒辦法下載RPM-GPG-KEY.dag.txt的話,可以下載我的備份檔案。(雖然沒什麼意義,他的伺服器掛點的話,之後yum也沒辦法安裝所需要的檔案啊。)

執行yum的安裝指令

[root@dspace ~]# yum -y install ffmpeg mencoder imagemagick python python-imaging

如果安裝都順利進行的話,那麼這個步驟就完成囉。

配置ZoomifyImage

ZoomifyImage的計畫網站中去下載最新版本,目前是1.4版,也可以下載我另外備份的檔案(24.06MB,有點大喔)。解壓縮之後,擺到/opt/當中 。

在Linux指令列之下,你可以用wget下載檔案,然後利用tar解壓縮,將之移至/opt/當中。指令如下:

[root@dspace ~]# wget http://nchc.dl.sourceforge.net/sourceforge/zoomifyimage/ZoomifyImage1_4.tar.gz
[root@dspace ~]# tar -zxvf ZoomifyImage1_4.tar.gz
[root@dspace ~]# mv ZoomifyImage /opt/

ZoomifyImage必須搭配Python才能執行,所以要記得一定要在之前yum安裝步驟中安裝Python喔。

配置JODconverter跟OpenOffice

請下載JODconverter的檔案,取出lib資料夾之中的所有jar檔(除了commons-cli-1.2.jar跟commons-io-1.4.jar之外),放到[dspace]/lib跟Java安裝目錄的擴增目錄(如果你跟我一樣安裝的是JDK 1.6.0.06,那麼路徑就是/usr/java/jdk1.6.0_06/jre/lib/ext/)之中。或是下載我另外備份的檔案:jodconverter-2.2.2-lib.zip

然後伺服器端必需要開啟OpenOffice的服務,用SSH開啟的話不知為何會失敗,請到本機端執行以下指令:

[root@dspace ~]# /usr/lib/openoffice.org2.0/program/soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard

出現以下訊息的時候,表示正常開啟:

image

同樣的,也把這段指令加入開機執行的指令當中吧。

修改/etc/rc.local

[root@dspace ~]# vim /etc/rc.local

請把這段指令「/usr/lib/openoffice.org2.0/program/soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard」加到最後去,這樣就大功告成了。

DSpace註冊Bitstream Format

image

開啟DSpace的管理介面,到「Bitstream Format Regisry(附加檔案(二進位檔案)格式登記)」當中,新增以下幾種檔案格式。支援等級皆選擇「已知」、取消「內部」的打勾。

MIME形式 名稱 完整描述 延伸檔名
video/x-msvideo AVI Audio Video Interleave avi
video/x-flv FLV Flash Video flv
video/x-ms-wmv WMV Windows Media Video wmv
application/vnd.rn-realmedia RM Real Media rm
application/vnd.rn-realmedia-vbr RMVB RealVideo Variable Bit Rate File rmvb
audio/3gpp audio/3gpp 3GPP Multimedia File 3gp
video/3gpp video/3gpp 3GPP Multimedia File 3gp
video/mp4v-es MP4 MPEG-4 Video File mp4
application/zip ZIP ZIP zip
audio/mpeg MP3 MPEG-1 Audio Layer 3 mp3
audio/mid MID Musical Instrument Digital Interface mid
audio/x-aac AAC Advanced Audio Coding aac
audio/flac FLAC Free Lossless Audio Codec flac
audio/ogg OGG Ogg ogg
audio/x-ms-wma WMA Windows Media Audio wma
audio/mp4a-latm M4A MPEG-4 Part 14 m4a
text/tab-separated-values TSV Tab-separated values tsv
text/csv CSV Comma-separated values csv

大部分的檔案格式在DSpace 1.5.0當中已經登錄了,特別是文件檔的類型,包括Microsoft Office跟OpenOffice系列,所以在此不特別敘述。目前我測試機上使用的Bitstream Format Registry資料表可以參考這個檔案,裡面有詳盡的設定,如果之後你發現有些格式沒有辦法正確辨識的話,再來比較這個檔案看看缺了哪些格式。

DSpace增加MediaFilter相關檔案

請將以下檔案下載,新增到指定的位置:

  • mediafilter-dspace-api.zip
    [dspace-source]/dspace-api/src/main/java/org/dspace/app/mediafilter/
  • BitstreamDisplay.zip
    [dspace-source]/dspace-jspui/dspace-jspui-api/src/main?java/org/dspace/app/webui/util/
  • RetrieveZIPServlet.zip
    [dspace-source]/dspace-jspui/dspace-jspui-api/src/main?java/org/dspace/app/webui/util/
  • extension-jspui.zip
    [dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp/extension/
    ※預設是沒有extension這個資料夾的,請自行建立。

如果你的DSpace是預設的狀態,並且安裝過我之前介紹的XMLMetadata,那麼可以採用我的檔案:

如果你這些檔案已經做過更動,那麼取代的話會造成你設計的功能消失。因此接下來我介紹怎麼調整這些檔案。

DSpace調整display-item.jsp

位置在[dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp/display-item.jsp

請在「<dspace:item-preview item="<%= item %>" />」程式碼之前,加入以下JavaScript跟CSS的引用語法,以便帶出jQuery UI等效果:

<script type="text/javascript" src="<%= request.getContextPath() %>/extension/jquery.js"></script> 
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/bitstream-display/thickbox.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/extension/bitstream-display/jquery-ui/js/jquery-ui-1.7.1.custom.min.js"></script> <script type="text/javascript" src="<%= request.getContextPath() %>/extension/bitstream-display/doDialog.js"></script> <link rel="stylesheet" href="<%= request.getContextPath() %>/extension/bitstream-display/thickbox.css" type="text/css" /> <link rel="stylesheet" href="<%= request.getContextPath() %>/extension/bitstream-display/jquery-ui/css/smoothness/jquery-ui-1.7.1.custom.css" type="text/css" />

 

DSpace調整web.xml

位置在[dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml

加入以下設定,讓DSpace擁有直接讀取zip檔案的功能:

  <servlet>
    <servlet-name>retrieve-zip</servlet-name>
    <servlet-class>org.dspace.app.webui.servlet.RetrieveZIPServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>retrieve-zip</servlet-name>
    <url-pattern>/retrieve-zip/*</url-pattern>
  </servlet-mapping>

 

DSpace調整ItemTag.java

位置在[dspace-source]/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/util/ItemTag.java

主要是使用BitstreamDisplay.java來分析、製作要顯示的連結。BitstreamDisplay.java詳細的用法我會另開一篇教,現在先簡介一下加入BitstreamDisplay功能的作法。

先在開頭引用BitstreamDisplay

import org.dspace.app.webui.util.BitstreamDisplay;

然後找到私有方法listBitstreams(),修改顯示連結的部份。請找到以下部份的程式碼:

                                // Work out what the bitstream link should be
                                // (persistent
                                // ID if item has Handle)
                                String bsLink = "<a target=\"_blank\" href=\""
                                        + request.getContextPath();

                                if ((handle != null)
                                        && (bitstreams[k].getSequenceID() > 0))
                                {
                                    bsLink = bsLink + "/bitstream/"
                                            + item.getHandle() + "/"
                                            + bitstreams[k].getSequenceID() + "/";
                                }
                                else
                                {
                                    bsLink = bsLink + "/retrieve/"
                                            + bitstreams[k].getID() + "/";
                                }

                                bsLink = bsLink
                                        + UIUtil.encodeBitstreamName(bitstreams[k]
                                            .getName(),
                                            Constants.DEFAULT_ENCODING) + "\">";

            					out
                                    .print("<tr><td headers=\"t1\" class=\"standard\">");
                                out.print(bsLink);
            					out.print(bitstreams[k].getName());
                                out.print("</a>");
                                

            					if (multiFile)
            					{
            						out
                                        .print("</td><td headers=\"t2\" class=\"standard\">");

            						String desc = bitstreams[k].getDescription();
            						out.print((desc != null) ? desc : "");
            					}

            					out
                                    .print("</td><td headers=\"t3\" class=\"standard\">");
                                out.print(UIUtil.formatFileSize(bitstreams[k].getSize()));
            					out
                                .print("</td><td headers=\"t4\" class=\"standard\">");
            					out.print(bitstreams[k].getFormatDescription());
            					out
                                    .print("</td><td class=\"standard\" align=\"center\">");

            					// is there a thumbnail bundle?
            					if ((thumbs.length > 0) && showThumbs)
            					{
            						String tName = bitstreams[k].getName() + ".jpg";
                                    String tAltText = LocaleSupport.getLocalizedMessage(pageContext, "org.dspace.app.webui.jsptag.ItemTag.thumbnail");
            						Bitstream tb = thumbs[0]
                                        .	getBitstreamByName(tName);

            						if (tb != null)
            						{
            							String myPath = request.getContextPath()
                                            	+ "/retrieve/"
                                            	+ tb.getID()
                                            	+ "/"
                                            	+ UIUtil.encodeBitstreamName(tb
                                            			.getName(),
                                            			Constants.DEFAULT_ENCODING);

            							out.print(bsLink);
            							out.print("<img src=\"" + myPath + "\" ");
            							out.print("alt=\"" + tAltText
            									+ "\" /></a><br />");
            						}
            					}

            					out
                                    .print(bsLink
                                            + LocaleSupport
                                                    .getLocalizedMessage(
                                                            pageContext,
                                                            "org.dspace.app.webui.jsptag.ItemTag.view")
                                            + "</a></td></tr>");

然後修改成以下程式碼,注意標註For Bitstream Display的部份:

                                // Work out what the bitstream link should be
                                // (persistent
                                // ID if item has Handle)
                                String bsLink = "<a target=\"_blank\" href=\""
                                        + request.getContextPath();
                                //For Bitstream Display
								String bdLink = request.getContextPath();
								
                                if ((handle != null)
                                        && (bitstreams[k].getSequenceID() > 0))
                                {
                                    bsLink = bsLink + "/bitstream/"
                                            + item.getHandle() + "/"
                                            + bitstreams[k].getSequenceID() + "/";
                                    //For Bitstream Display
                                    bdLink = bdLink + "/bitstream/"
                                            + item.getHandle() + "/"
                                            + bitstreams[k].getSequenceID() + "/";
                                }
                                else
                                {
                                    bsLink = bsLink + "/retrieve/"
                                            + bitstreams[k].getID() + "/";
                                    //For Bitstream Display
                                    bdLink = bdLink + "/retrieve/"
                                            + bitstreams[k].getID() + "/";
                                }

                                bsLink = bsLink
                                        + UIUtil.encodeBitstreamName(bitstreams[k]
                                            .getName(),
                                            Constants.DEFAULT_ENCODING) + "\">";
                                //For Bitstream Display
								bdLink = bdLink + UIUtil.encodeBitstreamName(bitstreams[k]
                                            .getName(),
                                            Constants.DEFAULT_ENCODING);
            					out.print("<tr><td headers=\"t1\" class=\"standard\">");
                                //out.print(bsLink);
            					//out.print(bitstreams[k].getName());
                                //out.print("</a>");
                                
                                //For Bitstream Display
                                BitstreamDisplay db = new BitstreamDisplay(item, bdLink);
                                out.print(db.doDialog(bitstreams[k].getName()));

            					if (multiFile)
            					{
            						out
                                        .print("</td><td headers=\"t2\" class=\"standard\">");

            						String desc = bitstreams[k].getDescription();
            						out.print((desc != null) ? desc : "");
            					}

            					out
                                    .print("</td><td headers=\"t3\" class=\"standard\">");
                                out.print(UIUtil.formatFileSize(bitstreams[k].getSize()));
            					out
                                .print("</td><td headers=\"t4\" class=\"standard\">");
            					out.print(bitstreams[k].getFormatDescription());
            					out.print("</td><td class=\"standard\" align=\"center\">");

            					// is there a thumbnail bundle?
            					if ((thumbs.length > 0) && showThumbs)
            					{
            						String tName = bitstreams[k].getName() + ".jpg";
                                    String tAltText = LocaleSupport.getLocalizedMessage(pageContext, "org.dspace.app.webui.jsptag.ItemTag.thumbnail");
            						Bitstream tb = thumbs[0]
                                        .	getBitstreamByName(tName);

            						if (tb != null)
            						{
            							String myPath = request.getContextPath()
                                            	+ "/retrieve/"
                                            	+ tb.getID()
                                            	+ "/"
                                            	+ UIUtil.encodeBitstreamName(tb
                                            			.getName(),
                                            			Constants.DEFAULT_ENCODING);

            							//out.print(bsLink);
            							//out.print("<img src=\"" + myPath + "\" ");
            							//out.print("alt=\"" + tAltText
            							//		+ "\" /></a>");
            							
            							//For Bitstream Display
            							String thumbnailImg = "<img src=\"" + myPath + "\" alt=\"" + tAltText
            									+ "\" />";
            							out.print(db.doDialog(thumbnailImg));
            							out.print("<br />");
            						}
            					}

            					//out.print(bsLink
            					//	+ LocaleSupport.getLocalizedMessage(pageContext,"org.dspace.app.webui.jsptag.ItemTag.view")
            					//	+ "</a>");
                                
                                //For Bitstream Display
                                out.print(db.doDialog(LocaleSupport.getLocalizedMessage( pageContext, "org.dspace.app.webui.jsptag.ItemTag.view")));
                                out.print("</td></tr>");

 

DSpace調整style.css.jsp

位置在[dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp/style.css.jsp

請新增一段程式碼,以讓Bitstream的列表置中:

table.miscTable, table.attentionTable {
	margin: auto;
}

效果圖片如下:

image

DSpace修改dspace.cfg設定

位置在[dspace]/config/dspace.cfg

最後,要加入設定,Media Filter才能生效。預設的Media Filter部份設定如下:

#### Media Filter / Format Filter plugins (through PluginManager) ####
# Media/Format Filters help to full-text index content or
# perform automated format conversions

#Names of the enabled MediaFilter or FormatFilter plugins
filter.plugins = PDF Text Extractor, HTML Text Extractor, \
				 Word Text Extractor, JPEG Thumbnail
# [To enable Branded Preview]: remove last line above, and uncomment 2 lines below
#   			 Word Text Extractor, JPEG Thumbnail, \
#   			 Branded Preview JPEG

#Assign 'human-understandable' names to each filter
plugin.named.org.dspace.app.mediafilter.FormatFilter = \
  org.dspace.app.mediafilter.PDFFilter = PDF Text Extractor, \
  org.dspace.app.mediafilter.HTMLFilter = HTML Text Extractor, \
  org.dspace.app.mediafilter.WordFilter = Word Text Extractor, \
  org.dspace.app.mediafilter.JPEGFilter = JPEG Thumbnail, \
  org.dspace.app.mediafilter.BrandedPreviewJPEGFilter = Branded Preview JPEG

#Configure each filter's input format(s)
filter.org.dspace.app.mediafilter.PDFFilter.inputFormats = Adobe PDF
filter.org.dspace.app.mediafilter.HTMLFilter.inputFormats = HTML, Text
filter.org.dspace.app.mediafilter.WordFilter.inputFormats = Microsoft Word
filter.org.dspace.app.mediafilter.JPEGFilter.inputFormats = BMP, GIF, JPEG, image/png
filter.org.dspace.app.mediafilter.BrandedPreviewJPEGFilter.inputFormats = BMP, GIF, JPEG, image/png

請修改成以下設定:

#### Media Filter / Format Filter plugins (through PluginManager) ####
# Media/Format Filters help to full-text index content or
# perform automated format conversions

#Names of the enabled MediaFilter or FormatFilter plugins
filter.plugins = PDF Text Extractor, HTML Text Extractor, \
				 Word Text Extractor, JPEG Thumbnail, \
FFmpeg Image Capturer, MEncoder Image Capturer, \
FFmpeg Video Converter, MEncoder Video Converter, \
Zoomify Image Converter, FFmpeg Audio Converter, \
ImageMagick Converter, OOg Text Converter, OOg PPT Converter, OOg PDF Converter

# [To enable Branded Preview]: remove last line above, and uncomment 2 lines below
#   			 Word Text Extractor, JPEG Thumbnail, \
#   			 Branded Preview JPEG

#Assign 'human-understandable' names to each filter
plugin.named.org.dspace.app.mediafilter.FormatFilter = \
  org.dspace.app.mediafilter.FFmpegVideoFilter = FFmpeg Video Converter, \
  org.dspace.app.mediafilter.FFmpegImgFilter = FFmpeg Image Capturer, \
  org.dspace.app.mediafilter.MEncoderFilter = MEncoder Video Converter, \
  org.dspace.app.mediafilter.MEncImgFilter = MEncoder Image Capturer, \
  org.dspace.app.mediafilter.ZoomifyFilter = Zoomify Image Converter, \
  org.dspace.app.mediafilter.FFmpegAudioFilter = FFmpeg Audio Converter, \
  org.dspace.app.mediafilter.PDFFilter = PDF Text Extractor, \
  org.dspace.app.mediafilter.HTMLFilter = HTML Text Extractor, \
  org.dspace.app.mediafilter.WordFilter = Word Text Extractor, \
  org.dspace.app.mediafilter.JPEGFilter = JPEG Thumbnail, \
  org.dspace.app.mediafilter.BrandedPreviewJPEGFilter = Branded Preview JPEG, \
  org.dspace.app.mediafilter.ImageMagickFilter = ImageMagick Converter, \
  org.dspace.app.mediafilter.OOgPDFFilter = OOg PDF Converter, \
  org.dspace.app.mediafilter.OOgTextFilter = OOg Text Converter, \
  org.dspace.app.mediafilter.OOgPPTFilter = OOg PPT Converter

#Configure each filter's input format(s)
filter.org.dspace.app.mediafilter.PDFFilter.inputFormats = Adobe PDF
filter.org.dspace.app.mediafilter.HTMLFilter.inputFormats = HTML, Text
filter.org.dspace.app.mediafilter.WordFilter.inputFormats = Microsoft Word
filter.org.dspace.app.mediafilter.JPEGFilter.inputFormats = BMP, GIF, JPEG, image/png
filter.org.dspace.app.mediafilter.BrandedPreviewJPEGFilter.inputFormats = BMP, GIF, JPEG, image/png
filter.org.dspace.app.mediafilter.FFmpegVideoFilter.inputFormats = MP4, MPEG, AVI, FLV, WMV
filter.org.dspace.app.mediafilter.FFmpegImgFilter.inputFormats = MP4, MPEG, AVI, FLV, WMV
filter.org.dspace.app.mediafilter.MEncoderFilter.inputFormats = RM, RMVB, video/3gpp, Video Quicktime
filter.org.dspace.app.mediafilter.MEncImgFilter.inputFormats = RM, RMVB, video/3gpp, Video Quicktime
filter.org.dspace.app.mediafilter.ZoomifyFilter.inputFormats = BMP, GIF, JPEG, image/png, TIFF
filter.org.dspace.app.mediafilter.FFmpegAudioFilter.inputFormats = MP3, AAC, FLAC, OGG, WMA, WAV, M4A
filter.org.dspace.app.mediafilter.ImageMagickFilter.inputFormats = TIFF
filter.org.dspace.app.mediafilter.OOgPDFFilter.inputFormats = Microsoft Powerpoint, OpenDocument Presentation, RTF, OpenDocument Spreadsheet, Calc 6.0 spreadsheets, CSV, TSV, Impress 6.0 presentations, Microsoft Word, OpenDocument Text, Writer 6.0 documents
filter.org.dspace.app.mediafilter.OOgTextFilter.inputFormats = Microsoft Powerpoint, OpenDocument Presentation, RTF, OpenDocument Spreadsheet, Calc 6.0 spreadsheets, CSV, TSV, Impress 6.0 presentations, OpenDocument Text, Writer 6.0 documents
filter.org.dspace.app.mediafilter.OOgPPTFilter.inputFormats = Microsoft Powerpoint, Impress 6.0 presentations, OpenDocument Presentation

# Where is Temp Directory?
filter.tempfile.config = /tmp/

# How to excute FFmpeg?
filter.exec.ffmpeg = ffmpeg
# FFmpeg Config
filter.FFmpegVideoFilter.config = -ar 22050 -ab 56 -f flv -y -s 320x240
filter.FFmpegImgFilter.config = -y -f image2 -ss 8 -t 0.001 -s 80x60
filter.FFmpegAudioFilter.config = -ar 22050 -y

# How to excute MEncoder?
filter.exec.mencoder = mencoder
# MEncoder Config
filter.MEncoderFilter.config = -vf scale=320:240 -ffourcc FLV1 -of lavf -lavfopts i_certify_that_my_video_stream_does_not_use_b_frames -ovc lavc -lavcopts vcodec=flv:vbitrate=200 -srate 22050 -oac lavc -lavcopts acodec=mp3:abitrate=56

# How to excute Python?
filter.exec.python = python
# Where is ZoomifyFileProcessor.py ?
filter.exec.zoomifyImage = /opt/ZoomifyImage/ZoomifyFileProcessor.py
# How to excute ImageMagick?
filter.exec.imagemagick = convert

簡單說明一下各設定檔的用處:

  • filter.plugins:要採用哪些filter,輸入他們的名稱。
  • plugin.named.org.dspace.app.mediafilter.FormatFilter:設定各個filter的名稱。
  • filter.org.dspace.app.mediafilter.*******.inputFormats:設定該filter處理的檔案類型,名稱是Bitstream Format當中的「名稱」,所以上面才要先設定Bitstream Format。
  • 以下都是各個filter的設定參數,可以做進一步的微調。如果你要在Windows環境底下執行的話,這些參數可能需要重新調整。

DSpace重新編譯

步驟如下,注意[tomcat]、[dspace-source]跟[dspace]要替換成你Tomcat的位置、原始碼跟安裝目的地的位置:

  1. cd [dspace-source]/dspace/
  2. mvn package
  3. cd [dspace-source]/dspace/target/dspace-1.5.0-build.dir/
  4. ant -Dconfig=[dspace]/config/dspace.cfg update
  5. \cp -rf [dspace]/webapps/* [tomcat]/webapps/
  6. [tomcat]/bin/shutdown.sh
  7. [tomcat]/bin/startup.sh

DSpace執行filter-media

請執行以下指令:

[root@dspace ~]# [dspace]/bin/filter-media -v

加入-v指令,程式就會顯示他處理的詳細過程,也可以看到剛剛新增的MediaFilter有沒有正確地被採用,如下圖:

image

如果有完成的話,就可以找一個有Bitstream的Item頁面看看這些功能有沒有出來囉。

image


因為MediaFilter結合了許多工具,儘管我已經盡量把安裝動作簡化了,但還是很繁雜,如果哪裡有出錯,請務必通知我。下一篇則是給進階者的BitstreamDisplay使用篇,讓你更進一步地去調整display-item或其他的版面。<-- Post Catalog -->

(more...)

Blogger 無限捲頁功能 (infinite scroll)

Blogger 無限捲頁功能 (infinite scroll)

17510352447 首頁圖

Google的Blog平台「Blogger」一直都有持續改進的消息,最近電腦玩物介紹了其中功能,其中一項是眾所期待的「無限捲頁」(或稱為無限捲動、自動換頁)。不過此功能目前尚未正式安裝,我看了好多Blogger也都沒有出現。所以我想就自己手動寫一個,並在此分享給有興趣的人來安裝。


無限捲頁功能介紹

傳統網頁中,使用者讀到網頁最下方時,必須要自己按換頁連結。在Blogger中就是「較舊的文章」。對使用者來說,每次都要按連結以重新讀取文章,還要載入文章之外的資料,往往會打擾使用者的閱讀節奏。

如果有無限捲頁的話,當使用者讀到網頁最下方時,程式會自動載入下一頁的網頁中顯示文章的內容,並貼到現在閱讀文章的最後面,讓使用者感覺就像是文章延長了一樣。

image

Google圖片搜尋也有應用到無限捲頁,這種使用經驗讓許多人稱讚不已。

使用者也可以自行安裝Auto Pager套件來擁有無限捲頁的功能。FirefoxChrome都可以使用,詳情請看電腦玩物的介紹。不過Auto Pager的功能不一定能配合的上Blogger,也不是每個使用者都有裝Auto Pager。那麼自己動手改造,讓Blogger具備無限捲頁功能,這樣也是不錯的作法。

由於Blogger並沒有提供大量換頁的功能,使用者只能一直按「較舊的文章」,因此也是相當適合使用無限捲頁功能。

Blogger官方將無限捲頁稱為「Auto Pagination」(自動換頁)。為了避免混淆,這邊我是用「Infinite Scroll」(無限捲頁)來稱之。一方面也是因為我是用jQuery的Infinite Scroll插件來建構此功能的緣故。不知道何時Blogger才會正式安裝此功能,在這之前,你也可以像我一樣裝個無限捲頁來玩玩。


Blogger文章數量與設定不合的問題

2011-06-25_231655 文章數量設定

Blogger可以在後臺中設定主頁最多顯示的文章數量,注意,是「最多」,而不是「固定」顯示。

因為Blogger有單頁最多顯示1MB的限制,如果當文章的內容過多的時候,首頁可能會因為這個限制而顯示不出設定中的文章數量。「布丁布丁吃什麼?」的首頁時常只會顯示一篇文章,可能就是這個原因。

不少Blogger的使用者都有提出這個問題,官方的解決方案之一是使用「繼續閱讀」功能,降低每一篇文章的顯示字數,就可以在單頁中顯示較多文章量。不過我自己用布丁式自動摘要功能,懶得每次都要插入「繼續閱讀」,所以這方案並不適合我。

另一個方案是官方推薦自己的「Auto Pagination」(自動換頁)功能,讓人閱讀到最後時自動載入下一頁的內容,就不會有單頁顯示文章數量過少的問題。不過至今「Auto Pagination」還是沒有實裝,所以我乾脆自己寫一個吧!


安裝無限捲頁

2011-06-25_233326 設計 網頁元素

進入Blogger的管理介面,進入「設計」的「網頁元素」,在頁尾的地方新增小工具。

2011-06-25_233246 新增JavaScript

選擇其中的HTML/JavaScript。

image

標題留空,內容貼上以下無限捲頁的程式碼:

<script src='http://www.google.com/jsapi' type='text/javascript'></script>
<script type='text/javascript'>google.load("jquery","1.2.6");</script>
<script src='https://sites.google.com/site/puddingchen35/blogger-infinite-scroll/jquery.blogger.infinitescroll.js' type='text/javascript'></script>
<script src='https://sites.google.com/site/puddingchen35/blogger-infinite-scroll/blogger-infinite-scroll-setup.js' type='text/javascript'></script>
<script type="text/javascript">
setup_blogger_infinite_scroll({
loadingImg: "https://sites.google.com/site/puddingchen35/blogger-infinite-scroll/infinite-scroll-loading.gif",
loadingText: "<em>下一頁讀取中……</em>"
});
</script>

儲存設定,這樣就完成了。

設定無限捲頁

在無限捲頁的程式碼中,可以設定兩個地方,位於以下範圍中:

<script type="text/javascript">
setup_blogger_infinite_scroll({
loadingImg: "https://sites.google.com/site/puddingchen35/blogger-infinite-scroll/infinite-scroll-loading.gif",
loadingText: "<em>下一頁讀取中……</em>"

});
</script>

  1. loadingImg:讀取時顯示的圖片。預設是用Infinite Scroll提供的讀取動畫,我傳到了自己的空間去:
  2. loadingText:讀取時顯示的文字。預設是「<em>下一頁讀取中……</em>」,可以使用HTML語法。

這個功能是改自jQuery的Infinite Scroll插件,雖然名字跟大部分功能都是一樣的,但是為了適應Blogger的版型,我修改了它原本自動判斷換頁連結的演算法,改成每一次都是去找尋「較舊的文章」的連結來讀取。修改後的Infinite Scroll插件檔案可以由此下載


比較Endless Scroll跟Infinite Scroll

annotation_tool_original

題外話,其實我在論文系統當中也用了無限捲頁的技巧。上圖中下面的標註列表就是用無限捲頁來實作,一開始JavaScript只會讀取少量標註,但隨著使用者往下捲動,JavaScript會讀取更多標註,直到沒有其他標註為止。

當時用的是jQuery的Endless Scroll插件,它比較適用於建構新功能時使用。Infinite Scroll則比較偏向修改既有功能,主要是以找尋下一頁連結而插入新文章的方式來實作無限捲頁。


結語

儘管一開始看到電腦玩物的介紹時,我還蠻期待Blogger趕快推出無限捲頁的功能,但事後想想,「布丁布丁吃什麼?」版面被我改了這麼多,Blogger要加入功能,恐怕也沒這麼容易。既然如此,那就自己改吧。一直以來我都是這樣走過來的。

做到一半的時候,我也有想過要不要將它設計成「小工具」(Gadget for Blogger)。研究了半天,才發現原來第三方的小工具是用iframe來實作,這樣就不能像無限捲頁一樣跟Blog內容有所互動,只能放棄這個途徑,乖乖插入HTML/JavaScript小工具吧。

不過經過這一課,又學到一些東西,感覺還是挺令人開心的就是。

(more...)

使用GitHub做定期爬蟲、保存與提供資料 / Building a Scheduled Crawler and Storing as Accessible Data by GitHub Action and Pages

布丁布丁吃布丁

使用GitHub做定期爬蟲、保存與提供資料 / Building a Scheduled Crawler and Storing as Accessible Data by GitHub Action and Pages

2023-0207-213215.png

網路爬蟲的教學很多,但要怎麼定期執行爬蟲、又要把爬完的資料保存後供人取用,則是一個大問題。

(more...)

從PHP的Fat-Free Framework框架來看用Node.js的Express框架開發網站的心得 / Fat-Free Framework in PHP vs. Express in Node.js: Pros and Cons of Node.js

從PHP的Fat-Free Framework框架來看用Node.js的Express框架開發網站的心得 / Fat-Free Framework in PHP vs. Express in Node.js: Pros and Cons of Node.js

image

之前我跟學弟嘗試使用Node.js的Express框架來開發網站。本想著前端跟後端都可以統一使用JavaScript,但實際嘗試之後才發現Express框架問題頗多。這兩天為了把Zotero的資料庫寫成網站,我回頭使用PHP的Fat-Free Framework框架來開發,順手程度讓我感動到痛哭流涕,不禁想要寫一篇來整理一下Node.js的各種問題。這篇就閒聊一下吧。

(more...)

用AppsGeyser把網站轉換成Android APP / The Usage of AppsGeyser: From Website to Android APP

用AppsGeyser把網站轉換成Android APP / The Usage of AppsGeyser: From Website to Android APP

appsgeyser_log_beta

我使用AppsGeyser把「布丁布丁吃什麼?」做成APP,並製作一個簡單的投影片介紹如何操作,並記錄把網站做成APP的一些看法。

I used AppsGeyser to package my blog: "pulipuli.blogspot.tw" as a Android's APP. I share a slide about usage of AppsGeyser and my thoughts of WebApp.


從網站與APP / From Web Site to APP

很多人都想把他們的網站做成APP,但是其實很多網站只要遵守自適應網頁設計原則(Responsive Web Design),就能夠直接用手機的瀏覽器開啟。

Android的APP具備Webview的功能,能夠讓你在APP中開啟指定網址。而AppsGeyser則是提供了一個Android APP框架,讓你建立一個開啟指定網址的APP,並協助你上傳到Google Play

Blogger提供了27種行動版的網頁介面。行動版的網頁介面簡化了網頁排版,將連結按鈕加大,方便行動裝置使用者用手指點選。以「布丁布丁吃什麼?」來說,以下兩個網址各別呈現桌面版與行動版的樣貌:

桌面版網址會偵測瀏覽器的類型,在行動裝置上開啟桌面版網址則會自動轉到行動版的網頁去。這次我就是用「布丁布丁吃什麼?」的行動版網頁來製作APP。

AppsGeyser的操作 / Guide of Apps Geyser

很多人都在網頁上介紹過AppsGeyser。我為了方便跟大家分享而製作了投影片。

APP下載 / Download APP

chart

手機直接掃描這個QR Code,你就可以把我的APP下載到你的手機囉。


讓網頁看起來像是APP:WebApp / Make Website Like A APP: WebApp

用Blogger的行動介面可以快速產生一個適合在行動裝置上瀏覽的網頁介面。不過這還不太像一個APP。作為一個良好的APP,我們需要讓操作元件遵守作業系統的設計規範。至少也要做個像是可以按的按鈕之類的操作介面,才會讓使用者覺得這會是一個APP。這種網頁一般就稱之為WebApp

jquery mobile

現在很多網頁元件提供了類似APP的操作元件,例如jQuery Mobile就內建了上圖的選單與按鈕,iUI更是讓你的網站一整個就像是APP。你可以重新設計網頁版面,套用jQuery Mobile或iUI提供的框架(framework),然後你就可以在網頁瀏覽器中操作一個像是APP的網頁了。

如果用AppsGeyser把長得像APP的WebApp製作成真的APP,那就會像是真的在使用APP一樣。

用網頁語法製作APP:PhoneGap / Using HTML5 to Create a APP: PhoneGap

如果你覺得在APP中開啟網頁來操作一個長得像APP的WebApp感覺非常彆扭,或是你覺得AppsGeyser提供的介面與功能太過簡陋或是多餘(我其實對AppsGeyser強迫隱藏手機的通知列這點感到困擾),那何不試著用你原本的網頁程式碼來製作一個真正的APP呢?

phonegap

PhoneGap是一個用HTML、CSS與JavaScript來製作iOS、Android、Windows Phone等行動裝置的APP的免費工具。在這裡你不需要額外去學習Android的程式語言JAVA、也不用學習iOS的程式語言Object-C,而是用你原本就會的網頁寫作語言:HTML、CSS與JavaScript來製作APP。

基本上PhoneGap也跟AppsGesyer很像,他一樣是製作一個內建Webview的APP,讓你用APP開啟手機瀏覽器來顯示你用PhoneGap撰寫的網頁。不過PhoneGap還提供了許多用JavaScript呼叫的函式庫,讓你在網頁語法中使用手機內建的元件,還有相機、儲存裝置、定位系統等網頁之外的功能。

使用PhoneGap來開發APP,你可以讓你的WebApp不只長得像是APP,還能夠使用行動裝置上面的其他功能,讓它更像是一個APP。

最近看到我常用的IDE NetBeans也支援PhoneGap的開發,真讓我感到躍躍欲試啊。

網站就是APP: Firefox OS / HTML5 Website Is a APP: Firefox OS

上面的WebApp都是在APP中利用Webview內嵌一個瀏覽器以顯示一個長得像是APP的網頁。不論是用jQuery Mobile、iUI讓網頁長得像是APP,還是用PhoneGap讓網頁可以用手機的功能,他們都還不是一個APP。因為在Android上執行APP是要用Java撰寫,在iOS上執行APP是要用Object-C撰寫,要執行你寫的WebApp都還是得透過網頁瀏覽器。

FirefoxOS_Screenshot_Development_Build_2012-10-23

Mozilla抱持著開放自由的遠大志向,製作了Firefox OS。Firefox OS是一個真的是用HTML5(就是HTML、CSS、JavaScript等網頁技術)來撰寫APP的作業系統。我們撰寫Firefox OS的APP時不僅可以用HTML5的語法,還能用Firefox OS推廣成為網頁標準的Web APP。這下子不需要在PhoneGap裡面才能引用手機功能的函式庫,你可以在網頁裡面直接以JavaScript取用手機的功能了。


APP開發方式的選擇 / Selection of APP Development

原生語言 / Native Solution

許多人,特別是電腦訓練課程補習班,他們大多都認為要製作在手機上運作的APP,就必須要用手機原生的程式語言(Native Solution)來撰寫。你要在Android上開發APP,你就必須用JAVA來撰寫,並且上傳到Google Play;你要在iOS上開發APP,你就必須用Object-C來撰寫,並且上傳到App Store。

Unify 3D

f00e0d2be734d5ce3de73091586b1e8c

除了原生語言之外,你也可以利用Unify 3D這種程式開發工具來製作跨平台的APP。現在手機上許多遊戲,特別是在Android與iOS都能夠看到的APP,很多都是用Unify 3D來製作。根據網路上的評論,Unify 3D製作的效率並不會比原生語言還要差得太多。不過Unify 3D是付費軟體,製作並發佈APP的成本可不便宜喔。

HTML5

HTML5

那用自由免費又跨平台的HTML5來開發WebApp呢?就像我這一篇從AppGeyser,一路談jQuey Mobile、iUI、PhoneGap到Firefox OS。

但是其實很多人都對於WebApp抱持否定的看法,像是認為PhoneGap製作的APP在跨平台與執行效率上有著相當大的問題。而在iOS的App Store上,更是明言拒絕WebApp上架。在Android與iOS這種原生語言非HTML5的作業系統上,藉由網頁瀏覽器來執行HTML5的APP,當然執行效率與外觀都比不上原生語言來得好。這是可以預見的情景,我並不感到意外。儘管實際上我還沒有真正著手踏入製作APP,不過我對WebApp的期許卻是一直都比原生語言來得高。

原生語言與WebApp的爭論就像是早期程式語言的爭論,有人認為使用組合語言為專門機器開發效率比較高,高等語言還要經過編譯效率較差。但是現在普遍的認知卻是偏向以高等語言來進行開發,這是因為高等語言在學習與開發效率上比組合語言高上更多。

儘管HTML5的HTML、CSS與JavaScript實際上是比Java還要再高等一些的高等程式語言,但是門檻也不見得比較低,很多人在面對JavaScript的Event與CSS的Selector上都遇到很多困難。不過比起開發效率,我認為HTML5的價值在於他是一個開放、自由、普遍流行的程式語言。比起花很多時間寫一個APP,最後只能在單一平台上執行。我認為用HTML5撰寫一個可以在任意平台上執行的APP更有價值。

使用APP的自由 / The Freedom of APP

許多人都會受到手機與APP彼此之間的高耦合的限制。例如想要用某個APP而選擇了iOS,又因為選擇了iOS而無法使用Android上面的APP。這種大家已經習以為常的場景,背後卻是代表著壟斷、封閉的下場。

我認為裝置歸裝置、APP歸APP,這才是真正的自由。我們應該是要用開放自由的程式語言,來開發一個包括電腦、手機等任何平台都能夠共用的APP。就這點來看,Firefox OS相當具有前瞻性,也是我十分看好的未來方向。


結論:WebApp是網站行動化計畫的最佳方案 / Conclusion: WebApp is the Best Solution

最近很流行兩個名詞,一個叫做「雲端」,另一個叫做「APP」。儘管真正搞懂這兩個名詞的人並不多,但是我們也毫不免俗地提出了一個目標兼具開發「雲端化」與製作「APP」的計畫。

除非一開始就致力於APP的開發,不然大部分的計畫應該都會以製作網頁應用為起點。特別是圖書館常見的數位典藏、數位圖書館計畫,大家都是製作一個用瀏覽器就能閱覽的網站。以這個網站為基礎,將網站套上行動網頁的元件,就能夠製作出長得像是APP的網頁。接著再用PhoneGap打包網站本身,讓網站直接以APP的方式交到使用者手中。

這就能夠在成品中包含「雲端化」──資料保存在遠端伺服器,不論你用哪種裝置都能夠取用相同的資料,以及「APP」──用手機行動裝置也能夠取用計畫成果的內容。

這是我心中規劃的最佳方案,也是我目前亟欲推薦老師往這方向進行開發的目標──不過這跟我的論文沒有直接關係就是了。

最後,其實我剛剛把AppsGeyser製作的APP上傳到Google Play去了,還付了750元台幣註冊成為Google Play的開發者。不過目前還不能在Google Play中搜尋到我的APP「布丁布丁吃什麼?」。我猜想可能是因為發佈後得過幾個小時才能看到結果吧?那晚點可以在Google Play找到我的APP的時候,我再跟大家說囉。

(more...)