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

論文進度報告(2010/7/24):從Model到View

布丁布丁吃布丁

論文進度報告(2010/7/24):從Model到View

image

上週末終於把Model的部份寫完了,也就是說,PHP的部份告一段落,而接下來要進入JavaScript的部份。進度的概況大概就是完成上面那張圖中的右下角部分,而其他都還要繼續做。

類別 應完成 已完成 已完成百分比 程式語言
Model 100 100 100% PHP
View 73 0 0% JavaScript
Controller 9 0 0% PHP
總計 182 100 54.9%  

以程式數量來看進度的話,就是上面的表格。大概完成了一半,現在進入下半場:JavaScript。


Model完結之傷感

回顧一個月多前開始撰寫PHP的部分時,需要熟悉PHP物件導向、CodeIgniter、撰寫PHPDoc、單元測試的開發方法,讓我摸索了好一段時間。到現在Model完成了,單元測試也十分稱職地完成了他的工作,現在又要暫時把它擱在一旁,是覺得有點捨不得。

我在撰寫Model時,實際上撰寫的檔案數量為141個,細表如下:

類別 數量
Libriaries 107
Extension Libraries 4
Languages 1
Helpers 3
Unit Test 24
Views 2
總數 141

在開發過程中有不少重新思考UML架構的時候。如果繪製UML時能夠熟悉CI跟PHP的話,也許那時系統分析的數量會更為準確也說不定。

image

同時,這些程式全部通過單元測試。單元測試共34頁、456項測試。這是程式品質的一種保證。

而且我也把核心的工具整理成toolkit類別,包括Generic Object、Collection、Iterator等等,在未來開發PHP專案時又可以將之拿來使用,是非常寶貴的程式結晶。

雖然是這樣說,但到目前為止,仍然沒有什麼很令人亮眼的東西可以拿出來跟大家講。Meeting報告時看老師聽我講這些抽象的東西感覺像是有點無聊,我也沒有辦法orz

要看到實際上可以用的程式,還要把View跟Controller組合起來才有可能。再怎樣也是急不得的。


View開始之困惑:學習撰寫JavaScript

說要完結Model、開始撰寫View之後,已經過了一個禮拜了。老實說,到現在我還是很困惑,我到底該怎麼辦才好?跟一個多月以前寫PHP一樣,JavaScript是一個我很熟悉的程式語言,但是我一直沒有很正式地去學習它。說到JavaScript,一般會想到表單驗證啦、jQuery動畫特效啦,但是我要寫的層次是比這些都還複雜很多,是要靠一個指令,帶出整個標註工具,並且要由純JavaScript來跟伺服器溝通、交換資料。這種在別人網頁上進行從無到有的架構,對我來說是從未經歷過的高難度境界。

為了要將JavaScript嚴謹地架構起來,最近我開始學習更多相關技術:

JSDoc

JavaScript很自由,但是因為太過自由了,所以他的物件導向特性並不是很明確。JSDoc是類似JavaDoc的JavaScript專用程式註解格式,一個搭配JSDoc的JavaScript程式大概會長得如下列程式碼一樣:

/**
* 驗證是否為陣列
* @param $obj
* @return 驗證結果
* @type boolean
*/
jQuery.is_array = function ($obj)
{
    return (typeof($obj) == 'object' && ($obj instanceof Array));
};

我們可以利用JSDoc來說明一個類別的繼承(@extends)、建構子(@constructor)、方法的參數(@param)、回傳資料(@return)與型態(@type)、所需類別(@requires)等等在程式中難以說明的特性,而使得我所使用的IDE:NetBeans可以正確地解讀複雜的JavaScirpt程式。

image

如果NetBeans能夠正確地解讀,那麼就能在撰寫程式時即時地提供說明與自動完成的功能。

然而,JSDoc卻是一把雙面刃。由於JavaScript並不像PHP是在伺服器端執行,而是必須讓使用者下載到他的電腦才能執行。也就是說,JavaScript的註解撰寫越多,表示它的體積越大、越吃網路流量。為此,我還需要一種JavaScript的壓縮打包技術。

Packer JavaScript en PHP

常常使用複雜framework,如jQuery、jQuery UI的人,一定有碰觸過min打包版程式的經驗。研究JavaScript打包技術(Packer)的Dean Edwards已經將之發展到很成熟的地步,而且支援.NET、perl,以及PHP來使用。

讓我們來看看打包到底能做到什麼程度:以下左圖是原始程式碼,總共13649位元組;右圖是打包壓縮的結果,只有5177位元組。壓縮到一半左右的大小,實在是很棒的成果。

原始程式碼(13649) 打包壓縮結果(4691)
image  image

打包壓縮其實並不是什麼很神祕的技術,大致上可以分成普通壓縮與Base 64、是否縮短變數名稱等選項。普通壓縮中,他刪除了註解、多餘的空格、對齊(tab)、換行,並將程式撰寫簡化,以節省空間。除此之外,使用Base 62則能獲得更高壓縮率的作法,在edikud的blog中介紹了一些提高壓縮率的守則,但似乎是寫錯了,所以我更正之後說明如下:

  1. 區域變數名稱前加上_,像是function的參數。例如「obj」改寫成「_obj」。
  2. 全域變數名稱前加上$。例如「TYPE_ID」改成「$TYPE_ID」。
  3. 最後補充一項:程式每行結尾都要加上「;」。原本JavaScript會把沒有「;」的換行補上「;」作為宣告的結尾,但是打包之後,換行會被刪去,所以就必須在一開始撰寫時就養成良好習慣地加上「;」才行。

至於測試方法則不是我關注的重點,所以就不特別去在意。

搭配CodeIgniter的網頁快取功能,我就可以把打包壓縮過後的JavaScript以cache的方式保存起來,既能在程式有所更改時立即地轉換成打包格式,也不會重複進行打包動作導致系統資源浪費。

QUnit:JavaScript的單元測試
image

為了養成撰寫程式時的良好習慣,即使是寫JavaScript,我也採用Unit Test單元測試的開發方法。Qunit是一個JavaScript的單元測試工具,用法也十分簡單,介紹網頁裡面已經把主要用法說明完畢。

只是我並不喜歡原始QUnit的介面與功能,所以修改了不少版面、加入型態判斷、例外捕捉並顯示錯誤訊息等功能,建立一個屬於我自己好上手的QUnit工具。透過QUnit來偵錯,我發現我逐漸改變以往使用alert來偵錯的方式,QUnit不僅能詳細地回報錯誤細節,速度與效率快上許多,因此更方便於跨瀏覽器之間的測試。

等待我把QUnit使用更熟悉之後,我會再把改良版的功能發佈出來跟大家共享。

JSONP:跨網域AJAX技術

以往在寫DSpace時,我是使用了很多AJAX技術,但那都是同一個網域底下的資料存取,而我的論文則是往更高的目標邁進:跨網域AJAX存取。

JSONP是一個天才想出來的非正式方案。這是利用<script>標籤的src載入JavaScript可以讀取不同網域的JavaScript檔的特性,只要在src的網址中以GET方式輸入參數,然後伺服器則回應相對應的資料並以JSON的方式讓使用者端的瀏覽器去做callback回呼的動作,就能夠達到跨網域AJAX的境界。詳細的作法可以先看看Hpyer的介紹

這個是很簡單的作法,但也是很危險的一種技術。因為參數以GET的方式傳遞,也就是說資料都會是以明碼來顯示,這就是一種容易被入侵的漏洞。對此我在思考利用公鑰私鑰的方式來為傳遞參數進行加密的作法,不過似乎是挺複雜的,我現在連基本的JavaScript物件導向都還沒熟悉呢。

此外,JSONP是以GET方式傳遞參數,而CodeIgniter又剛好最討厭GET傳遞參數。調整CodeIgniter讓他能支援JSONP,讓我花了不少功夫。等我真的把它馴服了,我們再來談談CodeIgniter的調整方法吧。

NetBeans對於JavaScript支援沒這麼強

習慣NetBeans對於PHP的支援之後,改寫JavaScript時就會明顯地感覺到不方便。這個不方便其實比起什麼功能都沒有的筆記本,或是只有語法顏色標示的編輯器來說還是強的很多。NetBeans提供了方法的自動完成(搭配JSDoc就如上述效果般)、可能錯誤的提示、自動補完()、{}、[]等括弧。

雖然不錯用,但跟PHP的支援比起來,少了兩個對我來說還蠻決定性的功能:

  1. 缺少變數名稱自動完成:也就是NetBeans無法取得JavaScript的變數前幾個字來猜出後面的字,這讓我寫function函數的時候就很不習慣,沒辦法快速帶出參數名稱。
  2. 方法自動完成的速度慢:要使用方法的自動完成,必須要在變數撰寫完畢之後加一個「.」以表示要使用方法時才會帶出來,但是選單速度非常慢,大概要等到1秒才完成,而且一旦自動完成輸入錯誤而想要回頭修改時,又必須要回到輸入「.」的情況才能再次帶出自動完成選單。非常地考驗耐心。

因此我正在找尋是否有NetBeans的Plugins能夠補強這兩個缺點,不然其他都很完美了說。


寫好系統只是興趣,並不代表研究順利

身為一個社會科學領域(表面上是文科)的研究生,我很瞭解理工科那種想把系統做好就算是完成論文研究的作法在這邊是行不通的。社會科學的研究講求結果分析能力,在好的環境中分析好的結果,在壞的環境下也可以分析出壞的原因,換句話說,不管研究對象的環境是好是壞,研究都能夠進行。

但是,其實我並不喜歡這種調調。並不是說這樣好或不好的問題,只是我個人看到問題擺在那邊,就會想要去動手解決,然後找出更好的方法之後,跟別人說明這些作法。就像我在這個Blog作的事情一樣,我的論文也是一樣的理念。

所以我不是用別人寫好但我並不滿意的系統,而是自我挑戰作一個系統,不僅只是作,而是以對我來說前所未有的嚴謹態度來撰寫一個系統。基於毛老師的理論,我今天用這種方式撰寫系統,以後我也應該會繼續用這種嚴謹的態度面對未來的工作,而這也是我的自我期許之一。

然而回到頭來,我很清楚這種想法至多仍只是能稱得上是一種興趣,而與研究進度無關。撰寫好系統,並不代表實驗能進行順利、問卷會設計、統計會分析,這些技術與知識都是不太相關的事情,而我則是抱著來一個我學一個的心態,一步一步慢慢地做完這個論文而已。

有些人也許看著我三不五時刻著系統進度,就以為我論文快完成了,但事實上兩者是不太有相關。那為什麼我要花這麼多時間在做系統呢?我會回答說這是一種興趣,更直接一點,我依然在耍任性,就跟寫書的時候一樣。

這種個性,真的是沒救啦。

(more...)

DSpace新增input-type——安裝篇

布丁布丁吃布丁

DSpace新增input-type——安裝篇

DSpace新增input-type目錄

  1. 安裝篇
  2. TextEditor篇
  3. FileUpload篇
  4. XMLMetadata篇

目前的DSpace 1.4.2~1.5雖然提供了很多input-type,像onebox(單欄)、textarea(多行)、dropdown(下拉式),但是使用起來還是覺得不夠,尤其是資料儲存無法像樹狀結構一樣靈活,在設計Metadata的時候更是一個很大的限制(雖然很多架設DSpace的人都沒有更動過input-forms的樣子)。

為了改進DSpace的不足,我以DSpace 1.5版開發出以下三種輸入表單型態:

texteditor

把原本的textarea以FCKeditor取代,並設定自動隱藏工具列。最後輸出資料則是html程式碼。

image

fileupload

在edit-metadata的步驟中,透過AJAX技術直接使用DSpace上傳步驟的功能。

上傳前:image

上傳後:image

xmlmetadata

可允許樹狀結構的metadata,並允許特定節點重複,可更靈活地儲存資料。一樣具備onebox、dropdown、textarea、texteditor、fileupload等多種輸入類型。

image

此篇介紹如何修改DSpace 1.5的各個檔案,以呈現出以上的功能,使用與設定則留到其他篇再講吧!


安裝環境與路徑縮寫說明

DSpace 1.5版本跟之前的1.4.2版本各程式存放的路徑上有很大的差別,此處我以1.5版為主,安裝在CentOS 5 Final上,JSP伺服器是Apache Tomcat 6.0.16

以下說明各路徑的簡寫:

[dspace-source]:DSpace原始檔的位置,通常位於「/opt/dspace-1.5.0-src-release/」

[dspace-jspui-webapp]:JSPUI原始碼路徑,通常位於「[dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp」之中。根據文件說明,修改之後存放的檔案請放到「[dspace-source]/dspace/modules/jspui/src/main/webapp/」之中。

[dspace-jspui-api]:JSPUI的API,通常位於「/opt/dspace-1.5.0-src-release/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/」。

[dspace-api]:DSpace的API,許多核心程式都擺在這邊,通常位於「[dspace-source]/dspace-api/src/main/java/org/dspace/」

[dspace]:DSpace安裝完成之後的目的地,包括input-forms.xml設定檔都要在這邊修改。預設的位置是「/dspace/」。

[tomcat]:JSP伺服器的安裝位置,通常是在「/opt/apache-tomcat-6.0.16/」裡面。

那麼以下就是安裝與修改的步驟囉!我會列出「路徑與檔名」、「我的程式碼下載」、「程式碼說明」以方便大家修改或安裝。因為細節繁雜,所以有可能有所遺漏,請在下方的回應欄盡量批評吧!


[dspace-jspui-webapp]/extension/...... (extension.20081018.zip)

這是一個新開啟的資料夾,裡面擺放我撰寫的JavaScript、CSS程式,請下載附件extension.zip,並擺放到該路徑去。裡面又分成幾個部份:(以下檔案所指的根目錄就是「[dspace-jspui-webapp]/extension/」,之後其他敘述也同這個道理)

/jquery.js:JavaScript的函式庫jQuery,在此使用的是1.2.1版本,讓JavaScript程式撰寫起來輕鬆很多,我撰寫的JavaScript都會用到jQuery。

/fckeditor/FCKeditor 2.6.3版本,一種JavaScript的網頁內嵌所見即得編輯器,可用類似Word的操作介面、建立HTML程式碼。用於input-type: texteditor,可改善DSpace 1.5只能顯示單調文字的缺點。

/fckeditor/fckeditor_display_toggle.js:用來自動隱藏/顯示工具列的函式。

/ajaxfileupload/ajaxfileupload.js:jQuery的Plugin,我改寫成適合DSpace檔案上傳的版本。

/ajaxfileupload/ajaxfileupload_fn.js:供input-type: fileupload呼叫ajaxfileupload.js的函式,裡面可設定fileupload的參數,但照以下安裝步驟的話是不需要修改此設定的。

/ajaxfileupload/ajaxfileupload_fnXML.js:給XMLMetadata呼叫ajaxfileupload.js的函式。

/xmlmetadata/dspace-inputtype-xml.js:XMLMetadata主要的JavaScript函式程式碼。

/xmlmetadata/dspace-inputtype-xml.css:XMLMetadata在呈現表單跟表格時的樣式表。

/xmlmetadata/ui.datepicker.js:jQuery的UI:Datepicker,可以方便挑選日期,用於XMLMetdata的input-type: date,我想這應該比DSpace原本的date好用很多。

/xmlmetadata/flora.datepicker.css:Datepicker的樣式表。

/display-item/metadata-value-display.js:在Display Item頁面時,針對fileupload跟xmlmetadata兩種資料調整呈現的樣貌。

這些程式只用短短的半個月時間寫完,也還沒做嚴格的測試,如果之後有改良的版本,會直接在此更新,並加上日期版本。


[dspace-jspui-webapp]/submit/...... (submit.20081015.zip)

這邊我修改了在進行上傳步驟的程式碼,以下一一說明。

/edit-metadata.jsp

image

DSpace描述階段的程式碼,為了能夠新增這三種input-type,在這一頁下了很多功夫。以下是修改細項:

修改1:引用所需的JavaScript跟CSS檔案。 請在<dspace>標籤開始之後插入這些引用吧。(紅色部份是新增的程式碼)

<dspace:layout locbar="off" navbar="off" titlekey="jsp.submit.edit-metadata.title">

<script type="text/javascript" src="<%= request.getContextPath() %>/extension/jquery.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fn.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fnXML.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor_display_toggle.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/ui.datepicker.js"></script>
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/flora.datepicker.css" type="text/css" media="screen" title="Flora (Default)">
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.css" type="text/css" media="screen">

修改2:定義要丟給新增函式的變數。包括workspaceItem的ID(目前遞交作業的ID)、nonInternalBistreams的ID(哪些是公開的檔案)、hasMultipleFiles(是否允許多檔案上傳)、defaultValue(XMLMetadata的預設呈現樣貌)。請加在設定inputType、label跟closedVocabulary的後面吧。(紅色部份是新增的程式碼)



       String label = inputs[z].getLabel();
       boolean closedVocabulary = inputs[z].isClosedVocabulary();

>     //For fileupload
     int workspaceItemID = si.getSubmissionItem().getID();
     Bitstream[] bitstreams = si.getSubmissionItem().getItem().getNonInternalBitstreams();
     ArrayList nonInternalBistreamsID = new ArrayList();  //new Int(bitstreams);
     for (int i = 0; i < bitstreams.length; i++)
         nonInternalBistreamsID.add(bitstreams[i].getID());
     boolean hasMultipleFiles = si.getSubmissionItem().hasMultipleFiles();
    
     //For XMLMetadata & other
     String defaultValue = inputs[z].getDefaultValue();

修改3:新增input-type呼叫函式的判斷式。在下方有if else的判斷式,程式會依照input-type的值不同,呼叫不同的函式,那麼我們就把這三個新增的函式也加入判斷之中,新增在list跟onebox之間吧。(紅色部份是新增的程式碼)

       else if (inputType.equals("list"))
       {
          doList(out, item, fieldName, dcSchema, dcElement, dcQualifier,
               repeatable, inputs[z].getPairs(), label);
       }
     else if (inputType.equals("texteditor"))
       {
         doTextEditor(out, item, fieldName, dcSchema, dcElement, dcQualifier,
                repeatable, fieldCountIncr, label, pageContext, vocabulary,
               closedVocabulary, request.getContextPath() + "/extension/fckeditor/");
       }
     else if (inputType.equals("fileupload"))
       {
         doFileUpload(out, item, fieldName, dcSchema, dcElement, dcQualifier,
              repeatable, fieldCountIncr, label, pageContext, vocabulary,
              closedVocabulary, workspaceItemID, nonInternalBistreamsID,
           hasMultipleFiles);
       }
     else if (inputType.equals("xmlmetadata"))
       {
         doXMLMetadata(out, item, fieldName, dcSchema, dcElement, dcQualifier,
                repeatable, fieldCountIncr, label, pageContext, vocabulary,
               closedVocabulary, workspaceItemID,
            nonInternalBistreamsID, hasMultipleFiles, defaultValue);
       }

       else
       {
         doOneBox(out, item, fieldName, dcSchema, dcElement, dcQualifier,
              repeatable, fieldCountIncr, label, pageContext, vocabulary,
              closedVocabulary);
       }

修改4:宣告doTextEditor()、doFileUpload()跟doXMLMetadata()。請寫在doList結尾之後吧(紅色的部份是新增的程式碼):

          }//end doList

    void doTextEditor(javax.servlet.jsp.JspWriter out, Item item,
      String fieldName, String schema, String element, String qualifier, boolean repeatable,
      int fieldCountIncr, String label, PageContext pageContext, String vocabulary, boolean closedVocabulary, String sBasePath)
      throws java.io.IOException
    {

      DCValue[] defaults = item.getMetadata(schema, element, qualifier, Item.ANY);
      int fieldCount = defaults.length + fieldCountIncr;
      StringBuffer sb = new StringBuffer();
      String val;

      if (fieldCount == 0)
         fieldCount = 1;

      for (int i = 0; i < fieldCount; i++)
      {
   if (i == 0)
      sb.append("<tr><td class=\"submitFormLabel\">")
        .append(label)
        .append("</td>");
   else
      sb.append("<tr><td> </td>");

         if (i < defaults.length)
           val = defaults[i].value;
         else
           val = "";

         sb.append("<td colspan=\"2\">");

         String textareaName = fieldName;
         if (repeatable && i>0)
            textareaName = textareaName + "_" + i;

         sb.append("<textarea name=\"")
           .append(fieldName);
         if (repeatable && i>0)
           sb.append("_").append(i);
         sb.append("\" rows=\"4\" cols=\"45\"")
           .append(hasVocabulary(vocabulary)&&closedVocabulary?" readonly=\"readonly\" ":"")
           .append("   >")
           .append(val)
     .append("</textarea>")
     .append(doControlledVocabulary(fieldName + (repeatable?"_" + i:""), pageContext, vocabulary));
      

       //FCKeditor ReplaceTextarea
      
       sb.append("<script type=\"text/javascript\">\n")
         .append("  var oFCKeditor = new FCKeditor( \"")
           .append(fieldName);
         if (repeatable && i>0)
           sb.append("_").append(i);
         sb.append("\" ) ;\n")
       .append("  oFCKeditor.Config[\"ToolbarStartExpanded\"] = false ;\n")
         .append("  oFCKeditor.BasePath  = \""+sBasePath+"\" ;\n")
         .append("  oFCKeditor.ReplaceTextarea() ;\n")
         .append("</script>\n");

     sb.append("</td>\n");

   if (repeatable && i < defaults.length)
   {
      // put a remove button next to filled in values
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
        .append("_remove_")
        .append(i)
//        .append("\" value=\"Remove This Entry\"/> </td></tr>");
        .append("\" value=\"")
           .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove"))
           .append("\"/> </td></tr>");
   }
   else if (repeatable && i == fieldCount - 1)
   {
      // put a 'more' button next to the last space
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
//        .append("_add\" value=\"Add More\"/> </td></tr>");
        .append("_add\" value=\"")
        .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.add"))
        .append("\"/> </td></tr>");
   }
   else
   {
      // put a blank if nothing else
      sb.append("<td> </td></tr>");
   }
      }

      out.write(sb.toString());
    }  //void doTextEditor()

    void doFileUpload(javax.servlet.jsp.JspWriter out, Item item,
      String fieldName, String schema, String element, String qualifier, boolean repeatable,
      int fieldCountIncr, String label, PageContext pageContext, String vocabulary, boolean closedVocabulary,
    int workspaceItemID, ArrayList nonInternalBistreamsID, boolean hasMultipleFiles)
      throws java.io.IOException
    {

      DCValue[] defaults = item.getMetadata(schema, element, qualifier, Item.ANY);
    fieldCountIncr = 1;
      int fieldCount = defaults.length + fieldCountIncr;
      StringBuffer sb = new StringBuffer();
      String val;
    
      if (fieldCount == 0)
         fieldCount = 1;

      for (int i = 0; i < fieldCount; i++)
      {
   if (i == 0)
      sb.append("<tr><td class=\"submitFormLabel\">")
        .append(label)
        .append("</td>");
   else
      sb.append("<tr><td> </td>");

         if (i < defaults.length)
           val = defaults[i].value.replaceAll("\"", """);
         else
           val = "";
      
    if (!val.equals(""))
    {
      String[] tempID = val.split("/");
      if (tempID.length > 2)
      {
        //String id = tempID[(tempID.length - 2)];
      int id = Integer.parseInt(tempID[(tempID.length - 2)]);
      
      int index = nonInternalBistreamsID.indexOf(id);
      if (index == -1)  continue;
      }
    }
      
         String inputName = fieldName;
         if (repeatable && i>0)
           inputName = inputName + "_" + i;


       sb.append("<td colspan=\"2\">");

    if (hasMultipleFiles == true)
    {
    sb.append("\n<input type=\"text\" id=\""+inputName+"\" name=\""+inputName+"\" value=\""+val+"\" style=\"display:none\"   />\n")
      .append("  <input type=\"file\" onchange=\"jQuery(this).nextAll('button.fileupload-do:first').click()\" />\n")
      .append("  <span></span>\n")
      .append("  <button class=\"fileupload-do\" onclick=\"return ajaxFileUpload(this, "+workspaceItemID+", '"+label+"');\" type=\"button\">Upload</button>\n");
      
       if (!val.equals(""))
       {
      sb.append("  <script type=\"text/javascript\">")
        .append("  ajaxFileUploadExist(\""+inputName+"\");")
        .append("</script>");
       }
    }  //if (hasMultipleFiles == true)
    else
    {
      sb.append("Please check \""+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.initial-questions.elem3")+"\"! \n");
    }
       sb.append("</td>\n");
    

   if (repeatable && i < defaults.length)
   {
      // put a remove button next to filled in values
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
        .append("_remove_")
        .append(i)
//        .append("\" value=\"Remove This Entry\"/> </td></tr>");
        .append("\" value=\"")
           .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove"))
           .append("\" style=\"display:none\" ")
      .append("/>")
      .append("<button type=\"button\" onclick=\"ajaxFileUploadRemove(this, "+workspaceItemID+")\">"+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove")+"</button>")
      .append(" </td></tr>");
   }
   else if (repeatable && i == fieldCount - 1)
   {
      // put a 'more' button next to the last space
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
//        .append("_add\" value=\"Add More\"/> </td></tr>");
        .append("_add\" value=\"")
        .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.add"))
        .append("\"/> </td></tr>");
   }
   else
   {
      // put a blank if nothing else
      sb.append("<td>")
      .append("  <button class=\"fileupload-cancel\" onclick=\"return ajaxFileUploadCancel(this, "+workspaceItemID+")\" style=\"display:none\" type=\"button\">"+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove")+"</button>\n")
      .append(" </td></tr>");
   }
      }

      out.write(sb.toString());
    }  //end of doFileUpload

    void doXMLMetadata(javax.servlet.jsp.JspWriter out, Item item,
      String fieldName, String schema, String element, String qualifier, boolean repeatable,
      int fieldCountIncr, String label, PageContext pageContext, String vocabulary, boolean closedVocabulary,
    int workspaceItemID, ArrayList nonInternalBistreamsID, boolean hasMultipleFiles, String defaultValue)
      throws java.io.IOException
    {

      DCValue[] defaults = item.getMetadata(schema, element, qualifier, Item.ANY);
      int fieldCount = defaults.length + fieldCountIncr;
      StringBuffer sb = new StringBuffer();
      String val;

      if (fieldCount == 0)
         fieldCount = 1;

      for (int i = 0; i < fieldCount; i++)
      {
   if (i == 0)
      sb.append("<tr><td class=\"submitFormLabel\">")
        .append(label)
        .append("</td>");
   else
      sb.append("<tr><td> </td>");

         if (i < defaults.length)
           val = defaults[i].value;
         else if (defaultValue != null)
           val = defaultValue;
     else
       val = "";
    
         sb.append("<td colspan=\"2\">");

         String textareaName = fieldName;
         if (repeatable && i>0)
            textareaName = textareaName + "_" + i;

         sb.append("<textarea id=\""+textareaName+"\" name=\"")
           .append(fieldName);
         if (repeatable && i>0)
           sb.append("_").append(i);
         sb.append("\" rows=\"4\" cols=\"45\"")
           .append(hasVocabulary(vocabulary)&&closedVocabulary?" readonly=\"readonly\" ":"")
           .append("   >")
           .append(val)
     .append("</textarea>")
     .append(doControlledVocabulary(fieldName + (repeatable?"_" + i:""), pageContext, vocabulary));
      

       //XMLMetadata Form Creator
       if (!val.equals(""))
     {
       sb.append("\n\n<script type=\"text/javascript\">\n")
         .append("var xm = new XMLMetadata(\""+textareaName+"\");\n")
         .append("xm.workspaceItemID = "+workspaceItemID+";\n")
         .append("xm.fieldTitle = '"+label+"';\n");
       if (hasMultipleFiles == false)
       {
         sb.append("xm.hasMultipleFiles = false;\n");
        sb.append("xm.langNotHasMultipleFiles = \"Please check \'"+LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.initial-questions.elem3")+"\'!\";\n");
       }
      
       if (nonInternalBistreamsID.size() != 0)
       {
         String nIBIDary = "[";
         for (int b = 0; b < nonInternalBistreamsID.size(); b++)
         {
          if (b > 0)
            nIBIDary = nIBIDary + ",";
          nIBIDary = nIBIDary + nonInternalBistreamsID.get(b);
         }
         nIBIDary = nIBIDary + "]";
         sb.append("xm.nonInternalBistreamsID = "+nIBIDary+";\n");
       }
       sb.append("xm.createRootForm();\n");
       sb.append("</script>\n");
      }

     sb.append("</td>\n");

   if (repeatable && i < defaults.length)
   {
      // put a remove button next to filled in values
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
        .append("_remove_")
        .append(i)
//        .append("\" value=\"Remove This Entry\"/> </td></tr>");
        .append("\" value=\"")
           .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.remove"))
           .append("\"/> </td></tr>");
   }
   else if (repeatable && i == fieldCount - 1)
   {
      // put a 'more' button next to the last space
      sb.append("<td><input type=\"submit\" name=\"submit_")
        .append(fieldName)
//        .append("_add\" value=\"Add More\"/> </td></tr>");
        .append("_add\" value=\"")
        .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.button.add"))
        .append("\"/> </td></tr>");
   }
   else
   {
      // put a blank if nothing else
      sb.append("<td> </td></tr>");
   }
      }

      out.write(sb.toString());
    }  //void doXMLMetadata()

%>

doTextEditor()跟doXMLMetadata()是參考doTextArea()而來的,而doFileUpload()則是參考doOneBox()。

修改5:移除doPersonalName()、doSeriesNumber()跟doTwoBox()的寬度限制。不知為何這些函式都把表格寬度鎖死了,他們裡面都會擁有類似的程式碼,我舉doPersonalName()的設定為例:(紅色為刪除寬度限制的程式碼,附近的註解則是原本有限制寬度的程式碼)

      //Width hints used here to affect whole table
      headers.append("<tr><td> </td>") //.append("<tr><td width=\"40%\"> </td>")
             //.append("<td class=\"submitFormDateLabel\" width=\"5%\">")
       .append("<td class=\"submitFormDateLabel\">")
//             .append("Last name<br>e.g. <strong>Smith</strong></td>")
       .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.lastname"))
       .append("</td>")
             //.append("<td class=\"submitFormDateLabel\" width=\"5%\">")
      .append("<td class=\"submitFormDateLabel\">")
//             .append("First name(s) + \"Jr\"<br> e.g. <strong>Donald Jr</strong></td>")
       .append(LocaleSupport.getLocalizedMessage(pageContext, "jsp.submit.edit-metadata.firstname"))
       .append("</td>")
             //.append("<td width=\"40%\"> </td>")
      .append("<td> </td>")
             .append("</tr>");
      out.write(headers.toString());

修改6:修改欄位寬度配置。每一列輸入欄位中間都會間隔一個調整欄位寬度用的列,我們可以藉由調整這一列來控制欄位大小,但前提是要先移除掉上面那幾個函式裡面的寬度限制。(紅色部份是修改過後的值,表示左右各保留10%留白,中間則是有80%的寬度。)

<%-- HACK: Using this line to give the browser hints as to the widths of cells --%>
       <tr>
         <td width="10%"> </td>
         <td colspan="2" width="80%"> </td>
         <td width="10%"> </td>
       </tr>
/upload-file-list.jsp跟/get-file-format.jsp

image upload-file-list.jsp

image get-file-format.jsp

前者上傳檔案之後的列表、後者是當上傳檔案格式不明時會提示使用者選擇檔案格式的網頁。當這些網頁被讀取時,我想要轉換成以json格式呈現剛剛上傳檔案的資料(json是JavaSciprt常用的輕量級資料語言)。要讓upload-file-list.jsp跟get-file-format.jsp正常運作,必需要傳送以下參數:

  • workspace_item_id:遞交階段的ID,可由si.getSubmissionItem().getID()取得,這段程式碼寫在edit-metadata的修改2當中。
  • step:上傳檔案的階段,預設是「3」,以下教學會將之改成「2」。可在ajaxfileupload_fn.js裡面設定。
  • page:上傳檔案只會有1頁,輸入「1」即可。可在ajaxfileupload_fn.js裡面設定。
  • jsp:預設是由choose-file.jsp傳送過去,輸入值為「/submit/choose-file.jsp」即可。可在ajaxfileupload_fn.js裡面設定。
  • description:上傳檔案的敘述,可空白或不傳,我在fileupload的設定是會傳label的值。可在ajaxfileupload.js裡面設定。
  • submit_upload:要有值程式才知道是上傳的步驟,我則是固定填入「&gt;」。可在ajaxfileupload.js裡面設定。

修改:用網址設定呈現時轉址到顯示json的網頁。如果網址中包含「action=json」,那麼我就轉到另一個顯示json的網頁去。轉址的這段程式碼插入在<dspace>標籤前後,細節如下:(紅色部份為<dspace>標籤之外新增的程式碼)

  String action = (String) UIUtil.getOriginalURL(request);
if (action.indexOf("action=json") != -1)
{
   JSPManager.showJSP(request, response,
                    "/submit/upload-file-json.jsp");
}
else
{
%>

<dspace:layout locbar="off" navbar="off" titlekey="jsp.submit.get-file-format.title" nocache="true">

……

</dspace:layout>
<%
}  
%>
/upload-file-json.jsp

上面的修改之後會轉到這個網頁,這是我另外撰寫的程式。該程式會去取得最新上傳的檔案(專門指bitstream),回傳filename、url、format等參數。程式也很簡單,只有22行,原始碼如下:

<%@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %>
<%@ page import="org.dspace.core.Context" %>
<%@ page import="org.dspace.app.webui.servlet.SubmissionController" %>
<%@ page import="org.dspace.app.util.SubmissionInfo" %>
<%@ page import="org.dspace.content.Bitstream" %>
<%@ page import="org.dspace.app.webui.util.UIUtil" %>
<%
    // Obtain DSpace context
    Context context = UIUtil.obtainContext(request);    

  //get submission information object
    SubmissionInfo subInfo = SubmissionController.getSubmissionInfo(context, request);

Bitstream[] bitstreams = subInfo.getSubmissionItem().getItem().getNonInternalBitstreams();
Bitstream json_bitsream = bitstreams[0];  //bitstreams[(bitstreams.length-1)];  
%>
{
  error: '',
  filename: '<%= bitstreams[(bitstreams.length-1)].getName() %>',
  url: '<%= request.getContextPath() %>/retrieve/<%= bitstreams[(bitstreams.length-1)].getID() %>/<%= org.dspace.app.webui.util.UIUtil.encodeBitstreamName(bitstreams[(bitstreams.length-1)].getName()) %>',
  format: '<%= bitstreams[(bitstreams.length-1)].getFormatDescription() %>'
}

[dspace-jspui-webapp]/display-item.jsp (display-item.20081015.zip)

image

這是顯示item的頁面。我希望顯示的值能夠動態地調整成指定的樣式,而不是只有文字。像是XMLMetadata我希望能以樹狀結構呈現、fileupload則是呈現檔名。

修改1:引用JavaScript跟CSS檔案。作法同edit-metdata.jsp的修改1,程式碼如下:(紅色部份為新增的程式碼)

<dspace:layout title="<%= title %>">

<%
    if (handle != null)
    {
%>

<script type="text/javascript" src="<%= request.getContextPath() %>/extension/jquery.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/display-item/metadata-value-display.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fn.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/ajaxfileupload/ajaxfileupload_fnXML.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/fckeditor/fckeditor_display_toggle.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.js"></script>
<script type="text/javascript" src="<%= request.getContextPath() %>/extension/xmlmetadata/ui.datepicker.js"></script>
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/flora.datepicker.css" type="text/css" media="screen" title="Flora (Default)">
<link rel="stylesheet" href="<%= request.getContextPath() %>/extension/xmlmetadata/dspace-inputtype-xml.css" type="text/css" media="screen">



    <table align="center" class="miscTable">

修改2:呼叫metadataValueDisplay()。全部網頁顯示之後的最後,呼叫metadataValueDisplay()以動態調整內容的顯示。程式碼如下:(紅色的部份為新增的程式碼)

<%
    }
%>
    <p class="submitFormHelp"><fmt:message key="jsp.display-item.copyright"/></p>
  <script type="text/javascript">
  metadataValueDisplay("<%= request.getContextPath() %>");
  </script>

</dspace:layout>

老實說,這並不是很好的作法。建議能從JSP顯示的時候就能呈現檔案或XML的樹狀結構,而不是等網頁讀出來再用JavaScript動態修改。但這部份程式得花另一番功夫了。


[dspace-api]/...... (dspace-api.20081015.zip)

API的三個檔案,不小心改錯的話很容易讓整個DSpace都毀掉喔。

/submit/step/DescribeStep.java

此程式會依照input-type來決定資料的儲存方式,由於我新增的三種input-type都是跟著onebox、textarea來跑的,所以就跟他們一樣的設定即可。程式碼如下:(紅色部份為新增的程式)

            else if ((inputType.equals("onebox"))
                    || (inputType.equals("twobox"))
                    || (inputType.equals("textarea"))
          || (inputType.equals("texteditor"))
          || (inputType.equals("xmlmetadata"))
          || (inputType.equals("fileupload")))

            {
                readText(request, item, schema, element, qualifier, inputs[j]
                        .getRepeatable(), "en");
            }
/app/util/DCInput.java

DSpace並沒有預設值的功能,item template文件樣板只是預設該collection底下的item會擁有固定的值,但這跟我們一般認知的預設值:「每次新增欄位時,某些值是被預先載入的」有段差異,尤其是XMLMetadata是將表單的形式預先寫入才能運作,因此我們要幫他新增取得default-value的功能。

修改1:宣告內用變數defaultValue:(紅色部份是新增的程式碼)

    /** is the entry closed to vocabulary terms? */
    private boolean closedVocabulary = false;
    
    /**  for XMLMetadata  */
    private String defaultValue = null;

修改2:用get取得裡面的資料:(紅色部份是新增的程式碼)

        vocabulary = (String) fieldMap.get("vocabulary");
        //for XMLMetadata
        defaultValue = (String) fieldMap.get("default-value");

        String closedVocabularyStr = (String) fieldMap.get("closedVocabulary");

修改3:宣告公用函式getDefaultValue():(紅色部份是新增的程式碼)

  public boolean isClosedVocabulary() {
    return closedVocabulary;
  }
  
  /** for XMLMetadata */
  public String getDefaultValue()
    {
        return defaultValue;
    }

/core/Utils.java

原本DSpace在display item的頁面中,如果你存的資料裡面有用到一些特殊字元的時候,會被轉換成顯示於HTML的脫逸字元(Escape characters,都會以「&」開頭、「;」結尾),以避免這些可能是HTML程式碼的字元影響版面呈現或程式運作。這種脫逸字元跟C、Java、PHP等會用反斜線「\」來脫逸的方式不同,是HTML獨特的編碼。

在DSpace當中,將會被脫逸掉的字元如下:

  • 「&」→「&amp;」
  • 「"」→「&quot;」
  • 「<」→「&lt;」
  • 「>」→「&gt;」」

※HTML的拖曳字元,可以參考網頁:HTML Escape Characters: Complete List

由於我們用到了由FCKeditor轉換而來的HTML,也會使用XMLMetdata來儲存、顯示資料,我們就不能讓他把這些字元脫逸。接著我們就是addEntities()的內容,程式碼如下:(紅色表示註解掉字元脫逸的程式)

    public static String addEntities(String value)
    {
        if (value==null || value.length() == 0)
            return value;
        
        //value = value.replaceAll("&", "&");
        //value = value.replaceAll("\"", """);

        // actually, &apos; is an XML entity, not in HTML.
        // that's why it's commented out.
        // value = value.replaceAll("'", "&apos;");
        //value = value.replaceAll("<", "<");
        //value = value.replaceAll(">", ">");


        return value;
    }

由於存在同名字檔案在/app/util/Utils.java,千萬要小心不要覆蓋錯囉!


[dspace]/config/...... (config.20081015.zip)

調整DSpace的設定。

/input-forms.dtd

此文件定義了input-forms.xml的格式,我們在此處加上default-value的宣告。

修改1:宣告field裡面的default-value是可選擇性的:(紅色部份為新增的程式碼)

<!ATTLIST page number NMTOKEN #REQUIRED>
<!ELEMENT field (dc-schema, dc-element, dc-qualifier?, repeatable?, label, input-type, hint, required?, vocabulary?, default-value?) >
<!ELEMENT dc-schema (#PCDATA) >

修改2:宣告default-value這個元素:(紅色部份為新增的程式碼)

<!ELEMENT form-value-pairs (value-pairs)* >
<!ELEMENT value-pairs (pair)+ >

<!ELEMENT default-value (#PCDATA) >
/item-submission.xml

此處調整敘述階段(describe)跟上傳階段(upload)的順序。原本的順序如下

  1. 取得開始的資訊:他會問你一些問題,像是是否要上傳多個檔案、是否擁有多個標題之類的,你要打勾來決定。
  2. 敘述資料:填寫由input-forms.xml規定好的表單。
  3. 上傳檔案:上傳bitstream檔案。DSpace的設計理念是前面的敘述階段在敘述這個檔案,例如論文的作者、出版資訊等等。預設此階段是一定上傳資料,不過可以在dspace.cfg裡面取消這個設定。
  4. 確認資料:網頁會列出你之前填寫及上傳的資訊,並讓你回頭去修正。
  5. 授權條款的確認

修改:敘述階段與上傳階段互換。如果要在敘述階段上傳檔案,即使我們在上傳階段並不上傳資料,還是必須先經過上傳階段才行。因此我們將敘述階段跟上傳階段調換過來,程式碼如下:(紅色部份為兩個互換的階段)

     <!--Step 1 will be to gather initial information-->
    <step>
      <heading>submit.progressbar.initial-questions</heading>
    <processing-class>org.dspace.submit.step.InitialQuestionsStep</processing-class>
    <jspui-binding>org.dspace.app.webui.submit.step.JSPInitialQuestionsStep</jspui-binding>         
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.InitialQuestionsStep</xmlui-binding>
      <workflow-editable>true</workflow-editable>
    </step>  

      <!--Step 2 will be to Upload the item-->
      <step>
        <heading>submit.progressbar.upload</heading>
        <processing-class>org.dspace.submit.step.UploadStep</processing-class>
        <jspui-binding>org.dspace.app.webui.submit.step.JSPUploadStep</jspui-binding>
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.UploadStep</xmlui-binding>
        <workflow-editable>true</workflow-editable>
      </step>
          
      <!--Step 3 will be to Describe the item.-->
      <step>
        <heading>submit.progressbar.describe</heading>
        <processing-class>org.dspace.submit.step.DescribeStep</processing-class>
        <jspui-binding>org.dspace.app.webui.submit.step.JSPDescribeStep</jspui-binding>
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.DescribeStep</xmlui-binding>
        <workflow-editable>true</workflow-editable>
      </step>
   

    <!--Step 4 will be to Verify/Review everything -->
    <step>
        <heading>submit.progressbar.verify</heading>
        <processing-class>org.dspace.submit.step.VerifyStep</processing-class>
      <jspui-binding>org.dspace.app.webui.submit.step.JSPVerifyStep</jspui-binding>
    <xmlui-binding>org.dspace.app.xmlui.aspect.submission.submit.ReviewStep</xmlui-binding>
    <workflow-editable>true</workflow-editable>
      </step>
/dspace.cfg
此處有很多DSpace的設定,也包含了是否限制一定要上傳檔案的設定。修改的程式碼如下:(紅色部份為新增的設定) ##### Settings for Submission Process #####

# Should the submit UI block submissions marked as theses?
webui.submit.blocktheses = false

# Whether or not we REQUIRE that a file be uploaded
# during the 'Upload' step in the submission process
# Defaults to true; If set to 'false', submitter has option to skip upload
#webui.submit.upload.required = true
webui.submit.upload.required = false


DSpace操作步驟說明

以上修改完畢之後,還需要進行重新安裝的步驟。

DSpace 1.5版重新安裝:出處在說明文件DSpace System Documentation: Configuration and Customization ([dspace-source]/dspace/docs/configure.html#jspui,dspace.1.5.0.configure.zip),大致上步驟如下:

  1. 進入指令模式
  2. 移動到你的[dspace-source]/dspace/目錄
    > cd [dspace-source]/dspace/
  3. 執行ant編譯
    > mvn package
  4. 移動到你的[dspace-source]/dspace/target/dspace-[version].dir目錄
    > [dspace-source]/dspace/target/dspace-[version].dir
  5. 執行ant打包
    > ant -Dconfig=[dspace]/config/dspace.cfg update
  6. 停止你的Tomcat伺服器
    > cd [tomcat]/bin/
    > ./shutdown.sh
  7. 複製新的.war檔案到Tomcat的webpapps目錄:
    > \cp -rf [dspace]/webapps/* [tomcat]/webapps/
  8. 重新啟動Tomcat伺服器
    > cd [tomcat]/bin/
    > ./startup.sh


參考資料


本來想要一篇之內寫完的,沒想到內容已經長到令人看不下去的地步,連Windows Live Writer連帶的都變得很慢,這就是警告我該換一篇寫了。繼續努力!

(more...)

布丁版OCS特色介紹:第一部分 / Pudding version OCS Features: Part 1

布丁版OCS特色介紹:第一部分 / Pudding version OCS Features: Part 1

2016-02-03_010637

上一篇簡單地介紹了研討會開放原始碼管理系統Open Conference System (以下簡稱OCS),這篇我想來介紹一下我修改的地方。這次要介紹的是外表上的介面、翻譯、以及報名功能。另外還有OCS主要功能的投稿與審查功能,則留到下次再介紹。

(more...)

如何建構響應式的HTML表格:表格縮圖響應式設計方案 / How to Create a Responsive Table using Table Thumbnail

如何建構響應式的HTML表格:表格縮圖響應式設計方案 / How to Create a Responsive Table using Table Thumbnail

7-HTML_How_to_Create_a_Responsive.png

啊囉哈~~這裡是剛剛才發現在米塔颱風中曬衣服是不行的布丁。

為了因應大小不一的螢幕尺寸,現代的網頁都強調必須具備響應式設計(responsive web design)。響應式設計可以讓網頁在大螢幕上面看起來很自然,在小螢幕裡面也不會看起來太過彆扭。網頁上各種不同的元素都有其響應式設計的做法。其中最具爭議性的,就是表格元素<table>的響應式處理。

有別於主流的捲軸方案或是重新排版的CSS方案,這邊我要提出一種特別適合部落格文章的表格縮圖響應式設計方案。此做法是在小螢幕時以表格縮圖取代原本的表格,而使用者點選表格縮圖時,就會開啟一個新網頁,裡面呈現完整的表格內容。下面就讓我們來看看表格縮圖響應式設計方案怎麽運作的吧。

(more...)

DSpace擴增MediaFilter格式(展示篇)

布丁布丁吃布丁

DSpace擴增MediaFilter格式(展示篇)

DSpace擴增MediaFilter的目錄:

image

DSpace具備MediaFilter(多媒體過濾器,不過以下我還是會用MediaFilter來稱呼)的功能,負責處理DSpace中上傳到Item裡面的Bitstream,每一種Filter的功用各不相同,可以在[dspace]/config/dspace.cfg裡面調整。預設的DSpace提供了以下幾種功能的Filter:

  • JPEG、GIF、PNG跟BMP圖片製作索引縮圖(thumbnail)跟商標縮圖(brand)

    索引縮圖(thumbnail):預設為寬80px、80px以下大小。

    商標縮圖(brand):預設是寬600px、高600px以下大小。
  • HTML、PDF跟Word檔案內的文字抽取出來,以便搜尋引擎製作索引。
    image
    抽取出來的文字會存成txt檔案,但是有時候會抽出亂碼。

預設的功能僅有如此,但這對各種數位物件的應用來說已經不敷使用,因此我又撰寫了幾個MediaFilter來用,並寫了展示的功能,可以搭配display-item時使用。不過大部分功能都只針對Linux(特別是CentOS發行版)環境下使用,而且都是使用開放原始碼自由軟體。

這一篇主要是介紹我到底做出了什麼MediaFilter、怎麼展示,之後再介紹怎麼安裝。


內嵌視窗介面

image 

當安裝完所有功能之後,下面的Bitstream列表基本上跟原始的一樣,但是點下去開啟之後,就會打開jQuery UI的Dialog視窗,取代到原本就讓使用者直接下載的不方便。視窗畫面如下:

image

jQuery UI的Dialog提供了相當具有彈性的操作介面,搭配上面圖片的數字來看有四大功能:(1) 自定義的標題、滑鼠按著標題列則可拖曳視窗;(2) 關閉按鈕,可以隱藏視窗;(3) 下載按鈕,可以下載原始的Bitstream,當然,也可以設定為不開放下載按鈕;(4) 視窗大小調整。此外,使用者可以開啟很多個Dialog,然後隨心所欲地拖曳到使用者想要的地方,或是在不需要的時候關掉指定的Dialog。

必須要注意的是,如果沒有是先做過[dspace]/bin/filter-media的動作的話,以下支援的功能有些就會失效,而仍會連結到原始的Bitstream。

圖片支援

image

支援格式:JPEG、GIF、PNG、BMP、TIFF

播放器:Zoomify Express

搭配新版本的Zoomify播放器,提供大圖瀏覽的功能、無差段縮放(是以前在百年圖書館做Zoomify的進化版喔!)。此外,TIFF也支援索引縮圖的功能了!無損壓縮的TIFF檔案是許多數位典藏採用的共通格式,相信這個MediaFilter能夠接納TIFF,一定可以造福許多數位典藏網站。

影片支援

image 

支援格式:MPEG、AVI、FLV、WMV、MP4、RM、RMVB、3GP、MOV

播放器:FLV Player

就跟大部分影音分享網站(如YouTube)一樣,MediaFilter會把影片檔轉換成可以直接線上播放的FLV檔案。

此外,MediaFilter也會擷取影片第8秒的畫面作為縮圖。

聲音支援

image

支援格式:MP3、WMA、WAV、AAC、FLAC、OGG、M4V

播放器:XSPF Web Music Player

以上格式都會轉換成MP3檔案,再搭配播放器就能夠在網頁中直接播放,而不需要讓使用者再去開啟其他軟體才能聽到音樂。

文件支援

image

支援格式:Offcie系列的doc、ppt、xls;OpenOffice系列的odt、odp、ods、sxi、sxw、sxc;通用格式的rtf、csv、tsv;原本DSpace就支援的txt、html、pdf

播放器:使用使用者電腦內部的Adobe PDF Reader之類的軟體,我推薦採用XChange Viewer

以上格式都能夠轉換成PDF以供線上播放,並抽取出文字檔作為全文檢索的對象。此外,Power Pointer投影片系列,包括ppt、odp、sxi還可以擷取第一張投影片作為縮圖。

我有想過是否要轉換成圖片檔案來作為線上播放,但是考量到瀏覽介面需要另外設計、也不方便操作,故還是選擇採用最通用的PDF Reader。缺點是目前沒有加密PDF的功能,PDFBox不知為何不能生效。

多檔案網頁支援

image

你可以把檔案壓縮成zip檔案,只要包含index.html、index.htm、default.html、default.htm,系統就會自動把他當成網頁開啟。注意到上面圖片中的範例,這是一個包含多個檔案的jQuery UI範例檔,裡面包含了網頁、圖片、JavaScript、CSS檔案,現在只要壓縮成zip之後上傳到DSpace,就能夠直接在DSpace上直接開啟了!


好,以上是給使用者看得部份。接下來後面幾篇,就是給系統建置者看得安裝跟使用的部份囉!

(more...)

論文進度報告(2010/10/10):幾乎完成工具列

布丁布丁吃布丁

論文進度報告(2010/10/10):幾乎完成工具列

image

上次的進度報告之後又隔了兩個多禮拜,最近也是一直在研究JavaScript的基礎技術,然後一邊在Blog把整理好的東西寫出來,希望能給一同鑽研JavaScript的程式設計師作為參考。評估各種寫作方式並訂定之後,就是繼續兩個禮拜之前停頓的進度,而繼續把工具列的部分完成。現在工具列只差「通知」功能尚未實作,但這是要等標註功能實作之後才會有通知,接著才是繼續完成這部份的功能。接下來就是要整理標註功能的UML了呢,又是一項大工程。在這之前,先來整理目前的論文進度吧。

目前的WebApp端的進度

元件 預估程式數量 已完成 UML類別圖
toolkit 17 17 1
core 15 13 1
kals_window 6 6 1
kals_toolbar 30 24 2
kals_text 38+? 0 3
search 6+? 0 1
統計 112+? 60 (53.5%) 9

整體來看,目前完成了大約一半的程式。由於使用物件導向架構開發,每個細節元件都是取用於toolkit或core中的上層元件,在開發同時也會不斷地改良這些元件,因此系統開發的速度會越寫越快。當然,這只是理論上而已。

也許會有人注意到類別程式的數量跟之前比起來有所減少,從之前的114降低到了112。我嘗試簡化系統的功能跟開發的方式來讓整個專案能更快完成,目前是將「自訂外觀」、「個人相片」的功能取消,暫緩適應小螢幕的功能調整(toolkit設計時有考慮小螢幕,但不一定每個功能都能適用就是);而開發中也只對關鍵功能設計單元測試,並且緊對Firefox進行測試,Chrome、IE、Android則在未來有機會時再進行統一的檢查。

navigation

上圖是kals_toolbar中navigation的UML類別圖,這是工具列上各個視窗的功能設定。其中灰字的類別則是暫停開發的類別。詳細的UML類別圖,請參考KALS Wiki

至於專案時程仍暫時維持在19天後完成,也就是10月29日。能不能完成我也沒把握,只能說盡力而為而已。

一改再改的JavaScript物件導向繼承寫作方式

一開始我的JavaScript是用最原始的prototype繼承法,但是由於這種方式在使用上會有些問題,而且我需要使用上層類別的方法,所以又花了一段時間回頭找尋相關資料學習。

首先學習到的是call跟apply的用法,但是發現到JavaScript仍有傳址問題存在,所以又把所有的屬性與方法寫在類別的建構子裡面,不過那將會造成系統資源大量消耗的問題,不得不再找尋新的方法。

接著找到的是Dean Edwards的Base繼承類別,他的確是很好用,寫法簡潔明瞭、又有呼叫上層類別方法的this.base()可使用,但是我使用的Aptana Studio看不懂,而我另外又試用了五六種JavaScript IDE,一樣是看不懂。儘管我後來找到讓Aptana Studio能夠理解的Base繼承法,但是最後仍毅然決然地使用最原始的prototype繼承法。

這是因為我在找尋這些方法的期間,學習到了prototype利用call、或是經典的base方法來呼叫上層或甚至是其他類別的方法來繼承並覆寫,讓prototype的寫法有了更多彈性,而能夠滿足系統的需求。儘管寫起來是比Base繼承法繁雜許多,但是由於prototype繼承法容易分析,許多JavaScript IDE都能理解,也是JavaScript程式設計師應該要知道的基本功課,這是我最後選用prototype繼承法來開發的原因。

這段期間不僅是一直翻閱相關資料、測試方法的優劣,還把系統的所有程式改寫了三次之多,著實非常地花時間,但也因此學到了不少東西。

重新檢視單元測試的必要性

core

在撰寫toolkit跟core的時候,我會要求自己盡量撰寫單元測試,並且最好是能夠測試到所以功能,也就是要求高「測試覆蓋率(Code coverage)」。上圖是core的UML類別圖,其中橘色的小註解標示著這部份的系統有設計單元測試的意思。小小的15個類別裡面,就有7個單元測試,力求核心元件要穩定。

過於繁雜的單元測試

上述在核心的工具時還可以做,到後面設計UI時,設計單元測試就越來越麻煩。我是用setTimeout來設計成機器人的方式,利用jQuery去選擇物件來點選元件或輸入資料,用以測試功能是否正常運作。但是這個機器人也常常做出一般人沒辦法做到的功能,導致測試的水準是比實際上更為高。舉例來說,原本使用者不可能點到某些隱藏起來的按鈕,可是用jQuery去點就可以點到、使用者必須等待視窗關閉並開啟之後才能執行下一個動作,可是jQuery就是無視視窗開閉來進行動作,導致他找不到下一個視窗的功能。也許是我設計單元測試的方法不對,但目前這樣做的確是很累,也顯得不切實際。

總結以上,簡單來說有兩個問題:

  1. 設計單元測試比設計元件本身還花時間,頗有本末倒置的情況發生。
  2. 單元測試不見得切合真實手動操作的情況。畢竟UI設計不像之前的計算用或存取用的程式這麼單純。

因此有必要重新檢討單元測試的必要性。

選擇重要的功能設計單元測試

仔細想一想,不需要全部功能都進行單元測試,去拼那個「測試覆蓋率」實在是很浪費時間與精力,只需要針對重要的功能設計單元測試即可。具體來說,針對具有以下兩種條件的功能來設計單元測試最為實用:

  1. 關鍵功能:千萬不能出錯的功能,當然要用單元測試確保他的正確性。
  2. 多道手續:與其說是在意其正確性,不如說是檢查時因為手續太多很麻煩,不如設計成單元測試的機器人讓他自己跑出的目標功能,再看看哪裡有問題,可以省下許多手動操作的時間。

也就是說,即使很重要的功能,但是如果很簡單就可以手動進行測試,或是其他測試中已經會使用到的功能,那麼就不需要刻意地撰寫單元測試。

程式要經過測試才算是完成。我仍然堅持這個最低底線,但現在並不強求一定要設計成自動的單元測試,即使是手動測試也可以算是勉強及格。

缺乏多人合作的遺憾

寫著寫著來講一個額外的小故事。

有天晚上回到宿舍的時候,發現室友學弟的位置旁坐著另一位學生。

學弟是資科所的學生,研究題目是跟雲端相關,當然也是寫程式的工作。看起來他應該是跟他同學兩個人正在趕一個系統,我偷偷地瞄到了phpMyAdmin的介面,兩台Mac筆電外加一個外接螢幕通通擺在桌上,旁邊還有喝到一半的五十嵐珍奶,看來晚上是要熬夜拼進度了。

老實說,我很少與人一起分工合作地寫程式。教人寫程式的經驗很多,但是可以像學弟他們那樣,「那個帳號登入的部份就拜託你了。」「OK!」地將一個系統分成幾個部分、再各自完成的經驗則是相當地少。不過架伺服器的各功能分工倒是有的,教育部計畫的時候,但那畢竟是功能調整,不太像是寫程式的這種「創作」。

我知道這種分工合作經驗的重要性對於程式開發來說並不亞於技術知識,大型的、有價值的系統是無法、也沒有時間讓我自己一個人完成,而我知道這正是我的弱點。至今我仍然是一個人在寫著程式,就像是現在正在做的碩士論文一樣。

儘管如此,我也在學著讓自己的程式「更好讀」、「更容易讓人使用」。像是建立嚴謹的物件導向架構程式讓人方便使用、遵守PHPDoc、JSDoc、cssdoc這種具有標準規範的註解讓人容易閱讀、利用UML跟網站草圖讓人理解整個系統,使用甘特圖與Wiki共筆工具來模擬多人合作時的專案互動。

我希望我能夠有資格與未來的夥伴一起合作開發一個大型的系統,所以我要加油,嗯。

結語

現在最令人擔心的是,19天後到底能不能完成系統呢?即使系統完成,後面還有好多後續的動作要做,多到讓人現在不想要面對的事情。能在這學期畢業嗎?能在明年3月之前如期把論文交給國中圖嗎?問題實在是太多了。現在還會有人問我要不要繼續念書,還是去當兵,還是要去當資訊替代役,但是現在能不能畢業都不知道啊XD

總之,每天都努力地繼續寫程式、寫Blog記錄,努力地向前進吧。

題外話,國慶日當天寫進度報告真是格外值得紀念?

(more...)

「布丁布丁吃什麼?」加入圖片燈箱特效檢視功能 / New Feature: Lightbox

布丁布丁吃布丁

「布丁布丁吃什麼?」加入圖片燈箱特效檢視功能 / New Feature: Lightbox

image

簡單記錄一下,本Blog加入了圖片檢視器Lightbox燈箱的功能。現在點選圖片不會變成開新視窗或直接跳到圖片的網址,取而代之的是顯示黑色背景、完整圖片的檢視畫面。但是因為我的Blog樣板並非預設的Blogger範本,所以這個功能並不是為了給所有Blogger使用者來使用的,僅供進階網頁開發者參考。以下記錄建置此功能的過程。

(more...)

DSpace新增編輯語系檔功能

布丁布丁吃布丁

DSpace新增編輯語系檔功能

image

實驗版本:DSpace 1.5

DSpace要修改語系檔的時候步驟繁雜,還要考慮編碼的轉換,十分的麻煩。以前我有寫過一個DSpace中文化專用utf-8轉碼程式,但其實那個轉換的功能很陽春、也有些缺陷,不建議再使用。我修改了一下轉碼程式,設計了更完整的編輯介面,並能夠直接在DSpace系統中使用。

我的作法是把原本DSpace那邊僅允許伺服器管理者必須手動修改設定檔的動作,改成用Java檔案管理的方式去建立、修改,跟原本DSpace設計理念有所差別,但是對於初學者來說卻是比較方便的。只是還是要手動重新啟動Tomcat,這個我之後再來寫一個網頁端程式好了。

以下是安裝步驟與使用方法介紹:


安裝檔案

請下載下列檔案,並擺到指定的位置:

  • messages-edit-jspui.zip
    [dspace-source]/dspace/modules/jspui/src/main/webapp/dspace-admin/messages-edit.jsp
    [dspace-source]/dspace/modules/jspui/src/main/webapp/dspace-admin/messages-main.jsp
  • MessagesEditServlet.java
    [dspace-source]/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/admin/MessagesEditServlet.java
  • util.zip
    [dspace-source]/dspace-api/src/main/java/org/dspace/app/util/FileUtil.java
    [dspace-source]/dspace-api/src/main/java/org/dspace/app/util/EscapeUnescape.java
  • jquery.js (必需要用1.2.6以上版本)
    [dspace-source]/dspace/modules/jspui/src/main/webapp/extension/jquery.js

修改web.xml,增加「messages-edit」路徑

web.xml原本的位置在[dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml,但是如果您修改過這個檔案了,那麼應該要另外儲存一份修改過得檔案在[dspace-source]/dspace/modules/jspui/src/main/webapp/WEB-INF/web.xml

請在最後一行結尾標籤</web-app>之前加入以下設定:

  <!-- Plugin Messages Editor  -->
  <servlet>
    <servlet-name>edit-messages</servlet-name>
    <servlet-class>org.dspace.app.webui.servlet.admin.MessagesEditServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>edit-messages</servlet-name>
    <url-pattern>/dspace-admin/messages-edit</url-pattern>
  </servlet-mapping>

然後請把web.xml儲存在[dspace-source]/dspace/modules/jspui/src/main/webapp/WEB-INF/web.xml之中。

修改navbar-admin.jsp,增加編輯語系檔的連結

navbar-admin.jsp預設位置在[dspace-source]/dspace-jspui/dspace-jspui-webapp/src/main/webapp/layout/navbar-admin.jsp,修改之後應該另存一份檔案在[dspace-source]/dspace/modules/jspui/src/main/webapp/layout/navbar-admin.jsp。

請找到以下程式碼:

我們增加一段程式碼在它

  <tr class="navigationBarItem">
    <td>
      <img alt="" src="<%= request.getContextPath() %>/image/<%= (currentPage.endsWith("/statistics") ? "arrow-highlight" : "arrow") %>.gif" width="16" height="16"/>
    </td>
    <td nowrap="nowrap" class="navigationBarItem">
      <a href="<%= request.getContextPath() %>/statistics"><fmt:message key="jsp.layout.navbar-admin.statistics"/></a>
    </td>
  </tr>

之後(不要動到原本的程式碼喔),如下:

  <tr class="navigationBarItem">
    <td>
      <img alt="" src="<%= request.getContextPath() %>/image/<%= (currentPage.endsWith("/dspace-admin/messages-edit") ? "arrow-highlight" : "arrow") %>.gif" width="16" height="16"/>
    </td>
    <td nowrap="nowrap" class="navigationBarItem">
      <a href="<%= request.getContextPath() %>/dspace-admin/messages-edit"><fmt:message key="jsp.layout.navbar-admin.editmessages"/></a>
    </td>
  </tr>

修改語系檔

修改您現有的語系檔之前,要先確認您所使用的語系檔為何。您可以在dspace.cfg裡面找到default.language設定。如果設定為default.lanuage = en_US,那麼您所使用的語系檔名為預設的「Messages.properties」,如果不是en_US而是「zh_TW」,那麼檔名應該是「Messages_zh_TW.properties」。由於我是用中文撰寫的,所以現在要加入的語系設定也是中文,故我們以「Messages_zh_TW.properties」作為範例。

語系檔會在[dspace-source]/dspace/modules/jspui/src/main/resources/Messages_zh_TW.properties,請在最後加入以下設定:

jsp.layout.navbar-admin.editmessages = \u7de8\u8f2f\u8a9e\u7cfb\u6a94

jsp.dspace-admin.messages-main.title = \u7de8\u8f2f\u8a9e\u7cfb\u6a94
jsp.dspace-admin.messages-main.heading = \u7de8\u8f2f\u8a9e\u7cfb\u6a94
jsp.dspace-admin.messages-main.submit_saved = \u5df2\u7d93\u5132\u5b58\uff01\u8acb\u91cd\u65b0\u555f\u52d5Tomcat\u7db2\u9801\u4f3a\u670d\u5668\u4ee5\u770b\u5230\u4fee\u6539\u7684\u6210\u679c\u3002
jsp.dspace-admin.messages-main.submit_cancel = \u53d6\u6d88\u7de8\u8f2f
jsp.dspace-admin.messages-main.default.language = \u76ee\u524d\u9810\u8a2d\u7684\u8a9e\u7cfb\u662f\uff1a
jsp.dspace-admin.messages-main.change-default-language = \u5982\u9700\u8981\u8b8a\u66f4\u7cfb\u7d71\u9810\u8a2d\u8a9e\u7cfb\uff0c\u8acb\u4fee\u6539dspace.cfg\u4e2d\u7684default.language\u8a2d\u5b9a\u3002
jsp.dspace-admin.messages-main.edit = \u7de8\u8f2f\u8a9e\u7cfb\u6a94
jsp.dspace-admin.messages-main.create = \u5efa\u7acb\u65b0\u7684\u8a9e\u7cfb\u6a94
jsp.dspace-admin.messages-main.create.source = \u5f9e\u4f86\u6e90\u6a94\u6848\u8907\u88fd
jsp.dspace-admin.messages-main.create.to-file-name = \u5230\u65b0\u7684\u8a9e\u7cfb\u6a94\u540d\u7a31
jsp.dspace-admin.messages-main.lang-collision = \u4e0d\u80fd\u8ddf\u73fe\u6709\u7684\u8a9e\u7cfb\u6a94\u540d\u7a31\u91cd\u8907\uff01
jsp.dspace-admin.messages-main.lang-empty = \u8a9e\u7cfb\u6a94\u540d\u7a31\u4e0d\u80fd\u662f\u7a7a\u503c\uff01
jsp.dspace-admin.messages-main.lang-active = \u73fe\u5728\u4f7f\u7528

jsp.dspace-admin.messages-edit.title = \u7de8\u8f2f\u8a9e\u7cfb\u6a94
jsp.dspace-admin.messages-edit.editing-position = \u60a8\u6b63\u5728\u7de8\u8f2f {0} \u8a9e\u7cfb\u6a94
jsp.dspace-admin.messages-edit.filter.key = \u7d22\u5f15\u540d\u7a31\u5305\u542b
jsp.dspace-admin.messages-edit.filter.or = \u6216
jsp.dspace-admin.messages-edit.filter.and = \u4e26
jsp.dspace-admin.messages-edit.filter.key = \u986f\u793a\u5167\u6587\u5305\u542b
jsp.dspace-admin.messages-edit.filter.do = \u641c\u5c0b
jsp.dspace-admin.messages-edit.filter.reset = \u986f\u793a\u5168\u90e8
jsp.dspace-admin.messages-edit.function.create = \u65b0\u589e\u9805\u76ee
jsp.dspace-admin.messages-edit.function.scroll-to-button = \u79fb\u81f3\u9801\u5c3e
jsp.dspace-admin.messages-edit.function.scroll-to-top = \u56de\u5230\u9801\u9996
jsp.dspace-admin.messages-edit.table.key = \u7d22\u5f15\u540d\u7a31
jsp.dspace-admin.messages-edit.table.value = \u986f\u793a\u5167\u6587
jsp.dspace-admin.messages-edit.js.nowLoading = \u8b80\u53d6\u4e2d\u2026\u2026\u8acb\u7a0d\u5019\u2026\u2026
jsp.dspace-admin.messages-edit.js.deleteButton = \u522a\u9664
jsp.dspace-admin.messages-edit.js.deleteConfirm = \u78ba\u5b9a\u662f\u5426\u8981\u522a\u9664\uff1f
jsp.dspace-admin.messages-edit.js.insertButton = \u63d2\u5165
jsp.dspace-admin.messages-edit.js.moveUpButton = \u2191
jsp.dspace-admin.messages-edit.js.moveDownButton = \u2193
jsp.dspace-admin.messages-edit.js.error.heading = \u767c\u751f\u932f\u8aa4
jsp.dspace-admin.messages-edit.js.error.name = \u932f\u8aa4\u540d\u7a31
jsp.dspace-admin.messages-edit.js.error.message = \u932f\u8aa4\u8a0a\u606f
jsp.dspace-admin.messages-edit.js.error.ValueEmpty = \u4e0d\u80fd\u662f\u7a7a\u767d\uff01
jsp.dspace-admin.messages-edit.js.error.WithSlash = \u4e0d\u80fd\u5305\u542b\uff1a
jsp.dspace-admin.messages-edit.js.error.WithNewLine = \u4e0d\u80fd\u63db\u884c\uff01
jsp.dspace-admin.messages-edit.js.error.CharsetError = \u4e0d\u80fd\u5305\u542b\u975e\u82f1\u6578\u5b57\u7684\u6587\u5b57
jsp.dspace-admin.messages-edit.js.save.KeyEmptyError = \u986f\u793a\u5167\u6587\u300c[0]\u300d\u7f3a\u5c11\u7d22\u5f15\u540d\u7a31\uff01
jsp.dspace-admin.messages-edit.js.save.ValueEncodeError = \u89e3\u78bc\u5230\u300c[0]\u300d\u6642\u767c\u751f\u932f\u8aa4\uff01
jsp.dspace-admin.messages-edit.js.filter.processing = \u641c\u5c0b\u4e2d\uff0c\u8acb\u7a0d\u5019\u3002
jsp.dspace-admin.messages-edit.js.sortConfirm = \u6392\u5e8f\u9700\u8981\u4e00\u6bb5\u6642\u9593\u904b\u7b97\uff0c\u800c\u4e14\u6392\u5e8f\u4e4b\u5f8c\u7684\u7d50\u679c\u7121\u6cd5\u5fa9\u539f\uff0c\u8acb\u554f\u662f\u5426\u8981\u7e7c\u7e8c\uff1f
jsp.dspace-admin.messages-edit.js.filter.not-found = \u641c\u5c0b\u6c92\u6709\u7d50\u679c\u3002
jsp.dspace-admin.messages-edit.encode-demo.heading = \u6e2c\u8a66\u8f49\u78bc\u5668
jsp.dspace-admin.messages-edit.encode-demo.text-to-dspace = \u2190
jsp.dspace-admin.messages-edit.encode-demo.dspace-to-text = \u2192

修改dspace.cfg檔案

dspace.cfg位於[dspace]/config/dspace.cfg

請加入以下設定:

#---------------------------------------------------------------#
#---------------Other Plugin Configuration----------------------#

tomcat.dir = /opt/apache-tomcat-6.0.16/webapps/jspui
dspace.source.dir = /opt/dspace-1.5.0-src-release

上面的tomcat.dir跟dspace.source.dir請更換成您實際上Tomcat伺服器當中擺放DSpace的位置,以及DSpace原始碼的位置。如果您之前照我寫的DSpace安裝法的話,那目錄位置就是這樣沒錯。

重新編譯DSpace

步驟如下:

  1. 移至DSpace原始檔目錄中的dspace目錄。注意底下的[dspace-source]要替換成您DSpace原始碼的目錄。
    [dspace@dspace ~]# cd [dspace-source]/dspace/
  2. 執行mvn package指令
    [dspace@dspace dspace]# mvn package

    如果最後出現BUILD SUCCESSFUL,表示編譯成功。
  3. 移至底下的target/dspace-{您DSpace的版本號}-build.dir/目錄。注意,底下的1.5.0要替換成您DSpace的版本號,或是查查看target砥下的目錄即可。
    [dspace@dspace dspace]# cd target/dspace-1.5.0-build.dir/
  4. 執行ant指令:
    [dspace@dspace dspace-1.5.0-build.dir]# ant -Dconfig=[dspace:]/config/dspace.cfg update
  5. 複製DSpace安裝目錄底下的資料夾到Tomcat的webapps目錄之中。注意,底下的[dspace]要替換成您DSpace的安裝目錄,[tomcat]則是要替換成您Tomcat的安裝目錄。
    [dspace@dspace dspace-1.5.0-build.dir]# cd [dspace]
    [dspace@dspace dspace]# \cp -rf [dspace]/webapps/* [tomcat-6.0.16]/webapps/
  6. 重新啟動Tomcat。
    [dspace@dspace dspace]# cd [tomcat]/bin
    [dspace@dspace bin]# shutdown.sh
    [dspace@dspace bin]# startup.sh

功能說明:首頁

要使用編輯語系檔的功能,使用者必須要是管理者權限(Administrator)登入才行,一般的使用者是沒辦法使用的。

image

  1. 從左邊的導覽列可以進入「編輯語系檔」
  2. 偵測目前使用的語系:系統會取得dspace.cfg裡面的default.language設定,這就是您目前DSpace所使用的語系。
  3. 編輯語系檔:下拉式選單可以選擇您要編輯的語系檔。針對目前使用的語系檔,系統會特別在語系檔名稱後標明「現在使用」。
  4. 建立新的語系檔:您可以選擇一個來源檔案,然後將他複製到新的語系檔當中,再進行編輯動作。

功能說明:編輯頁

image

語系檔通常會高達一千多筆資料,進入編輯頁面的時候需要花一段時間讀取,請耐心等候。

  1. 顯示您現在編輯的語系檔名稱。
  2. 搜尋語系檔功能,當您要修改特定語系設定的時候非常好用。讀取完成之後,就可以按下後面的按鈕。
  3. 測試轉碼器:類似原來DSpace中文化專用utf-8轉碼程式的功能,這邊測試的資料並不會寫入語系檔。
  4. 功能列,包括更新、新增項目(在最後面新增一筆語系設定)、移至頁尾、取消(然後返回首頁)。讀取完成之後,就可以按下更新、新增項目的按鈕。
  5. 讀取中的訊息。有時候因為資料量過大,會導致網頁像是當機一樣,請稍後。
  6. 語系編輯器:
    1. 第一欄是索引名稱,請依照DSpace的設計方式,只輸入英文字(盡量小寫)、用「.」分隔索引名稱的層次,用「-」連接多字元片語。
      如果語系檔開頭使用#,則表示是註解,供系統人員閱讀用。編輯器會自動偵測並改變表現形式,如下圖:
      image
    2. 第二欄是顯示內文,也就是真正顯示在網頁上的資料。雖然可以使用一些簡單的HTML,但是不建議用太過複雜的內容,不然很容易導致語系檔錯誤,無法讀取。
    3. 刪除:刪除此設定,無法復原。
    4. 插入:在此設定之後加入一行設定。
    5. 上移與下移。

終於寫完了。

這樣要編輯DSpace語系檔就簡單多了吧。

接下來來作一個Tomcat重新啟動的功能,省得還要到伺服器端重新啟動DSpace吧。<-- Post Catalog -->

(more...)