2016年8月28日 星期日

Duration的格式化 - DurationFormatUtils - Java

這邊介紹一個Java很好用的類別,DurationFormatUtils,收錄在
Apache Commons Lang中,下載地址在這
package的位置在org.apache.commons.lang.time.DurationFormatUtils,
說明文件在這

DurationFormatUtils可以對代表 " 一段持續時間" 的資料做格式化,在DurationFormatUtils中
這個 "一段持續時間" 是用 millis-second 來的儲存的。

我們可以利用DurationFormatUtils來對持續時間用我們提供的自訂格式來格式化,例如181800000毫秒可以用 "dd'天'HH'時'mm'分'ss'秒'" 來表示成 "02天02時30分00秒"

下面就來紀錄一下DurationFormatUtils的使用方法:
import java.time.Duration;
import java.time.Instant;
import org.apache.commons.lang3.time.DurationFormatUtils;

public class DurationFormatUtilsTest {

    public static void main(String[] args) {
        Instant startInstant = Instant.parse("2015-05-01T00:00:00Z");  //ISO 8601 表示法
        Instant endInstant = Instant.parse("2015-05-03T02:30:00Z");        
        Duration duration = Duration.between(startInstant, endInstant); //得到兩個Instant差的Duration
        
        String resultDurationString1 = DurationFormatUtils.formatDuration(duration.toMillis(), "dd'天'HH'時'mm'分'ss'秒'");
        System.out.println(resultDurationString1); //輸出: 02天02時30分00秒
        
        String resultDurationString2 = DurationFormatUtils.formatDuration(duration.toMillis(), "dd'天'HH'時'mm'分'ss'秒'", false);
        System.out.println(resultDurationString2); //輸出: 2天2時30分0秒  //(10以下的數字不補0)
        
        String resultDurationString3 = DurationFormatUtils.formatDuration(duration.toMillis(), "HH'時'mm'分'ss'秒'");
        System.out.println(resultDurationString3); //輸出: 50時30分00秒  //(沒有設定小時以上的單位,小時以上全部由小時表示:50小時)
        
        String resultDurationString4 = DurationFormatUtils.formatDurationISO(duration.toMillis());
        System.out.println(resultDurationString4); //輸出: P0Y0M2DT2H30M0.000S //ISO 8601標準格式
        
    }
}

參考資料:

  1. Class DurationFormatUtils
  2. 常用jar包之commons-lang使用
  3. Java 8的日期與時間(Date-Time)API

2016年8月20日 星期六

使用Ajax上傳檔案 - (Javascript, JQuery or AngularJS) + FormData

在以前我們要在網頁中傳送檔案資料時,通常會需要設計一個html Form,並設定Form的encype=multipart/form-data和準備一個input type="file",在User按下submit按鈕後,跟據Form
所設定的action="URL",將整個頁面request移動到action所指定的地方,例如Servlet,
等處理完後在將User導至其他網頁頁面。

但有沒有可能不要讓User被導到其他頁面,留在原頁面就完成檔案資訊的傳送呢?
答案是可以的,這邊就要利用新的javascript類別FormData和Ajax的技術來達到Ajax傳
送檔案(也可順便傳遞其他input資料)資料。


在這邊我們要利用Netbeans、Servlet3.0、Tomcat 8來實做我們的範例,
實現了三個版本的檔案上傳:Javascript、JQuery、AngularJS
首頁是專案檔案結構,如下圖:

所用的版本JQuery為v2.0.3、AngularJS為v1.5.8,
主要的重要檔案有:

  1. fileUploadAjaxExample.html
    給User上傳檔案的html網頁。
  2. FileUploadAjax.js
    處理檔案資料上傳的Javascript File.
    包含Javascript、JQuery、AngularJS三個版本。
  3. FileUploadAction.java
    用來接收檔案資料的servlet,這裡設定URL patter為/fileUpload.do
  4. web.xml
    設定檔,其中必須在要接收檔案的servlet中設定的tag,
    tag裡可以有以下的子tag設定:
    <location>         檔案存放位置 (使用Part.write(fileName)可以在寫入檔案,但如果fileName
                               為絕對路徑則以絕對路徑為準)
    <max-file-size>  最大檔案size
    <max-request-size>   最大request size  (例如POST的request size)
    <file-size-threshold>
       超過file-size-threshold的檔案request將會以臨時暫存的方式存到硬                                       碟中,預設為0
接下來我以來各別看下每個實作檔案的內容:

2016年8月14日 星期日

使用FileReader讀取file資料 - javascript

FileReader是HTML5的新Javascript物件,可以用來讀取input type="file"的file資料(其實就是FileList對像),其實不只input type="file"的FileList對像,還可以讀取許多不同的資料來原,例如Blob對像、拖拉產生的DataTransfer對像等。

今天要來實作一個簡單的圖片預覽器,須求如下:

  1. 選擇檔案後,如果是可顯示的圖片檔,會在下面的"圖片預覽"顯示出圖片。
  2. 如果沒有選擇檔案(例如取消了檔案選取),則"圖片預覽"的圖片會被清掉。
  3. 為了簡單起見,這裡不檢查file來源是不是圖片檔。


程式實作如下:
說明:

  1. FileReader.readAsDataURL(source)可以輸入file source並得到一串當下可用的URL,
    型式為 dtat:..........,存在FileReader.result中
    還有其他的read函式可用,例如:
    readAsArrayBuffer()、readAsBinaryString()、readAsText()
  2. FileReader在讀取完後會觸發load事件,還有其他的事件可用,例如:
    onabort:當讀取操作被中止時調用.
    onerror:當讀取操作發生錯誤時調用.
    onload:當讀取操作成功完成時調用.
    onloadend:當讀取操作完成時調用,不管是成功還是失敗.該處理程序在onload或者onerror之后调用.
    onloadstart:當讀取操作將要開始之前調用.
    onprogress:在讀取數據過程中周期性調用.
  3. FileReader:
    FileReader.readyState代表資料的讀取狀態,總共有三個值:
    FileReader.EMPTY = 0:還沒有加載任何數據.
    FileReader.LOADING = 1:數據正在被加載.
    FileReader.DONE = 2:已完成全部的讀取請求.

源碼(怕JsFiddle有問題):
html:

圖片預覽:
javascript:
var fileUploader = document.getElementById("fileUploader");
var imageView = document.getElementById("imageView");
//用來讀取file資料的FileReader
var fileReader = new FileReader();

//監控#fileUploader的值變化
fileUploader.addEventListener("change", function(event) {
  if (this.files.length > 0) {
   //有選取file時,使用fileReader讀取file資料
    //readAsDataURL可以將讀取到的file資料轉成
  //data:......的URL型式,在讀取完後觸發load
  //事件,URL存在FileReader.result中
    fileReader.readAsDataURL(this.files[0]);
  }else{
   //沒有選取file時,例如選擇取消,
    //將的src設成""
   imageView.src = "";
  }
}, false);

//fileReader讀取完file資料後會觸發load事件
fileReader.addEventListener("load", function(event) {
 //讀取後設定的src
  imageView.src = this.result;
}, false);

參考資料:
  1. FileReader

2016年8月13日 星期六

自製檢查file size的directive (input type="file") - AngularJs

AngularJS的表單、欄位驗證(Validation)非常好用,但有時會碰到想要自訂驗證方式、
或是某個欄位AngularJS並沒有實作Validation時(例如input type="file"),
就需要自訂有客製化驗證能力的directive

在這邊的需求如下:

  1. 製作一個directive,名稱為file-validator
  2. 配合input type="file"使用
  3. 可以設定file size(大小,單位Byte)和file type(副檔名), 用法範例:
  4. 如果fileSize沒指定或小於等於0,則不對fileSize做限制驗證。
  5. 如果fileType沒指定或為空字串,則不對fileType做限制驗證。
完成的程式碼成品如下:


說明:

  1. 當我們在ng-form裡面的input設置name及ng-model (及ngModelController)後,如果AngularJS有實作此種input type類型的話,Angular會在View中的值或對應model的
    值改變時,進行View及model的雙向挷定、同時變更。
    ngModelController裡面會存放model的值,即ngModel.$modelValue。
    也會存放view(通常為input type中顯示的值),即ngModel.$viewValue。
    並且也會管理此input的valid狀態。

    但因為AngularJS並沒有對input type="file"進行實作,也就是,不管User在input中選了什麼檔案,皆不會存值到ngModel.$modelValue及ngModel.$viewValue中,當然雙向挷定的model中也不會有值。

    所以為們要帶自制的directive中,指定
    required : ngModel
    來得到管理這個input的ngModel,並且手動的設定model的值。
    (!!不要手動用ngModel.$setViewValue()設定viewValue的值,會導致model value 會被蓋掉)
  2. 因為要設定model的值,所以我們要引入$parse,$parse(code)可以代入一串程式碼,有點
    像eval,$parse(code)會返回一個函式,並且此函式有一個函式,assign(scope, value),
    可以在scope中執行程式碼,並將value賦值給程式碼執行得到的變數。
    例如:
    $parse("myCtrl.fileUpload").assign(scope, file);
    代表在scope中執行 :  myCtrl.fileUpload = file;
  3. 在ngModel中,可以使用
    ngModel.$setValidity( validType, isValid)
    來設定此input的validType是valid還是invalid。
  4. 在解析如以下字串時,
    file-validator="fileType:'png'; fileSize:333;"
    我們使用了兩個正規表達式:
    fileSize\s*:\s*(\d+)\s;

    fileType\s*:\s*(["'])(\w+)\1\s*;
    其中第二個正規達式中的 \1 的意思是跟第一個Group相匹配,而第一個Group就是(["']),
    也就是fileType:"png"; 或 fileType: 'png';可以匹配,但
                fileType:'png";  或 fileType:"png'; 不可以匹配,
    雙引號及單引號要成對使用。

原碼紀錄(怕jsFiddle出問題):
使用angular.js
html:

請選擇size小於3MB的PNG檔案


file valid: {{myForm.fileUpload.$valid}}
file size (Byte): {{myCtrl.fileUpload.size}}
副檔名錯誤
檔案過大


javascript:
 var myApp = angular.module("myApp", []);

 myApp.controller("myController", [function() {
   //
 }]);
 //自製的file validator
 myApp.directive('fileValidator', ["$parse", function($parse) {
   return {
     restrict: 'A',
     scope: true,
     require: 'ngModel', //代表管理input type="file"這個input的ngModelController,例如此例
     //ngModel.name = "fileUpload",
     //可以連結ng-model裡指定的變數值(ngModel.modelValue) 和
     //input view欄位中顯示的值(ngModel.viewValue)                          
     link: function(scope, elm, attrs, ngModel) {
       var expression = attrs.fileValidator;

       var fileSizeReg = /fileSize:\s*(\d+)\s*;/;
       var fileTypeReg = /fileType\s*:\s*(["'])(\w+)\1\s*;/;

       //規定file的size,單位Byte
       var fileSizeLimit = fileSizeReg.exec(expression)[1] || 0;
       //規定副檔名
       var fileTypeLimit = fileTypeReg.exec(expression)[2] || "";

       elm.bind('change', function() { //發現input type file值改變時
         scope.$apply(function() {
           var file = elm[0].files[0]; //取得file資料
           var fileType = /.+\.(.+)/.exec(file.name)[1];

           //$parse("程式碼")可以返回一個function,之後可以用這個function的
           //assign(scope, value)來在scope中進行賦值(value)動作,
           //例如: 在scope中墸行: 程式碼 = value (有點像eval())
           $parse(attrs.ngModel).assign(scope, file);

           //檢查file副檔名及size
           //用ngModel設置fileSize的valid
           if (fileTypeLimit === "" || fileType === fileTypeLimit) {
             ngModel.$setValidity("fileType", true);
           } else {
             ngModel.$setValidity("fileType", false);
           }
           //用ngModel設置fileSize的invalid
           if (fileSizeLimit <= 0 || file.size < fileSizeLimit) {
             ngModel.$setValidity("fileSize", true);
           } else {
             ngModel.$setValidity("fileSize", false);
           }
         });
       });
     }
   };
 }]);