2017年10月11日 星期三

以Java 由Url下載圖片

今天要來記錄如何使用Java來下載網路上的圖片(或檔案,此例只下載圖片),

Java可以對Url進行Http Request來取得Response,
得到input stream後將檔案儲存下來,

為了避免有些Server會擋程式的Request,
我們必須模仿瀏覽器,在Request中加上User-Agent的Header (或更多的其他Header,模仿的越像越不容易被擋),

如果圖片的Url是http的話比較簡單,
但如果是https的話,即SSL,那就要取得對方Server網站的憑證,
或是自己實作一個 X509TrustManager,來所有憑證檢查都通過,

如果是JDK 1.8 以下,可能會有此Exception :
Could not generate DH keypair
使用 JDK 1.8 就可解決此問題,
可參考 [Java] 處理無法透過SSL抓取網站資料的問題

以下為程式碼範例,說明都寫在註解中:


import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class ImageDownloader {

 public static void main(String[] args) {

  String fileName_http = downloadImageFromUrl("http://www.image.com/files/8813/5551/7470/cruise-ship.png","D:" + File.separator, "HttpImgTest");
  System.out.println("Http的圖片下載: " + fileName_http);
  
  String fileName_https = downloadImageFromUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Plithocyon_armagnacensis.JPG/220px-Plithocyon_armagnacensis.JPG","D:" + File.separator, "HttpsImgTest");
  System.out.println("Https的圖片下載: " + fileName_https);

 }

 public static String downloadImageFromUrl(String url, String fileDirectoryPath, String fileNameWithoutFormat) {
  String filePath = null;
  
  BufferedInputStream in = null;
  ByteArrayOutputStream out = null;
  HttpURLConnection httpUrlConnection = null;
  FileOutputStream file = null;

  try {
   
   if (url.startsWith("https://")) {
    //HTTPS時
    httpUrlConnection = getHttpURLConnectionFromHttps(url);
   }
   //如果不是HTTPS或是沒成功得到httpUrlConnection,用HTTP的方法
   if(httpUrlConnection == null) {
    httpUrlConnection = (HttpURLConnection) (new URL(url)).openConnection();
   }
   
   // 設置User-Agent,偽裝成一般瀏覽器,不然有些伺服器會擋掉機器程式請求
   httpUrlConnection.setRequestProperty("User-Agent",
     "Mozilla/5.0 (Linux; Android 4.2.1; Nexus 7 Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166  Safari/535.19");
   httpUrlConnection.connect();

   String imageType;
   if (httpUrlConnection.getResponseCode() == 200) {
    //成功取得response,
    //取得contentType
    String contentType = httpUrlConnection.getHeaderField("Content-Type");
    // 只處理image的回應
    if ("image".equals(contentType.substring(0, contentType.indexOf("/")))) {
     //得到對方Server提供的圖片副檔名,如jpg, png等
     imageType = contentType.substring(contentType.indexOf("/") + 1);

     if (imageType != null && !"".equals(imageType)) {
      //由HttpUrlConnection取得輸入串流
      in = new BufferedInputStream(httpUrlConnection.getInputStream());
      out = new ByteArrayOutputStream();

      //建立串流Buffer
      byte[] buffer = new byte[1024];

      file = new FileOutputStream(new File(fileDirectoryPath + File.separator + fileNameWithoutFormat + "." + imageType));

      int readByte;
      while ((readByte = in.read(buffer)) != -1) {
       //輸出檔案
       out.write(buffer, 0, readByte);
      }      

      byte[] response = out.toByteArray();
      file.write(response);      

      //下載成功後,返回檔案路徑
      filePath = fileDirectoryPath + File.separator + fileNameWithoutFormat + "." + imageType;
     }
    }

   }
  } catch (MalformedURLException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  } finally {
   //關閉各種串流
   try {
    if (out != null) {
     out.close();
    }
    if (in != null) {
     in.close();
    }
    if (httpUrlConnection != null) {
     httpUrlConnection.disconnect();
    }
    if (file != null) {
     file.close();
    }
   }catch (IOException e) {
    e.printStackTrace();
   }
   
  }
  return filePath;
 }

 public static HttpURLConnection getHttpURLConnectionFromHttps(String url) {
  HttpURLConnection httpUrlConnection = null;
  //建立一個信認所有憑證的X509TrustManager,放到TrustManager裡面
  TrustManager[] trustAllCerts;
  try {
   // Activate the new trust manager
   trustAllCerts = new TrustManager[] { new X509TrustManager() {

    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {     // TODO Auto-generated method stub
     //不作任何事
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {     // TODO Auto-generated method stub
     //不作任何事
    }

    public X509Certificate[] getAcceptedIssuers() {
     //不作任何事
     return null;
    }

   } };

   //設置SSL設定
   SSLContext sslContext = SSLContext.getInstance("SSL");
   sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
   HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());

   //跟HTTP一樣,用Url建立連線
   httpUrlConnection = (HttpURLConnection) (new URL(url)).openConnection();
  } catch (KeyManagementException e) {
   e.printStackTrace();
  } catch (NoSuchAlgorithmException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
  
  return httpUrlConnection;
 }

}


源碼下載:
ImageDownloaderFromHttpOrHttps.7z

參考資料:

2017年10月5日 星期四

正規表逹式 - Java 範例

紀錄下Java的正規表示法使用方法範例:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegTest {

 public static void main(String[] args) { 
  //(?i) 代表大小寫忽略
  //.*?為非貪婪演算,盡可能找最小範圍的結果值
  doRegularExpression("[ABC/def]kk[123/456][ABC/def]kk[123/456]", "(?i)\\[(a.*?)/(.*?)\\]");
  //.*為貪婪演算,盡可能找最大範圍的結果值
  doRegularExpression("[ABC/def]kk[123/456][ABC/def]kk[123/456]", "(?i)\\[(a.*)/(.*)\\]");
  
  /* 輸出為:
  ===========================
  測試的句子: [ABC/def]kk[123/456][ABC/def]kk[123/456]
  正規表示法: (?i)\[(a.*?)/(.*?)\]
  ---------------------------
  第1次匹配,找到2個Group:
  匹配的字串為: [ABC/def]
  第1個Group: ABC
  第2個Group: def
  ---------------------------
  第2次匹配,找到2個Group:
  匹配的字串為: [ABC/def]
  第1個Group: ABC
  第2個Group: def
  ===========================
  
  ===========================
  測試的句子: [ABC/def]kk[123/456][ABC/def]kk[123/456]
  正規表示法: (?i)\[(a.*)/(.*)\]
  ---------------------------
  第1次匹配,找到2個Group:
  匹配的字串為: [ABC/def]kk[123/456][ABC/def]kk[123/456]
  第1個Group: ABC/def]kk[123/456][ABC/def]kk[123
  第2個Group: 456
  ===========================
  */
 }
 
 static void doRegularExpression(String text, String regularExpression) {
  System.out.println("===========================");
  System.out.println("測試的句子: " + text);
  System.out.println("正規表示法: " + regularExpression);
  
  Pattern pattern = Pattern.compile(regularExpression);
  Matcher matcher = pattern.matcher(text);  
  
  
  for(int matchCount = 1 ; matcher.find(); matchCount++) {
   // groupCount不包括匹配的字串,即matcher.group(0)
   System.out.println("---------------------------");
   System.out.println("第" + matchCount + "次匹配,找到" + matcher.groupCount() + "個Group:"); 
   System.out.println("匹配的字串為: " + matcher.group(0));
   for(int groupCount = 1; groupCount <= matcher.groupCount(); groupCount++) {
          System.out.println("第" + groupCount + "個Group: " + matcher.group(groupCount));
      }
  }
  System.out.println("===========================");
  System.out.println();
 }

}


2017年6月22日 星期四

Gifsicle 和 Giflossy(Gifsicle的一個Github Fork) (nmake, visual studio)

Gifsicle是個功能強大的GIF處理程式,使用命令列視窗來執行,
可以將許多張圖合成一張GIF動畫、
查詢GIF的size (包括各frame)、
切割、提取、加入frame到GIF動畫中、
壓縮GIF等。

以下是它的資訊:
Gifsicle
官網 :  http://www.lcdf.org/gifsicle/
Github : https://github.com/kohler/gifsicle

gifsicle只提供了三種壓縮輸出,
-O1, -O2, -O3,其中-O3效果最好,
例如 cmd 指令:
gifsicle -O3 froSrc.gif  -o toSrc.gif
但對於一些GIF可能無法再壓縮的更小。

這時就可以使用Giflossy來幫忙了。
Giflossy是一個在Github上fork了gifsicle的一個分支專案,
擴充了gifsicle的功能,加了一個參數,--lossy=XX
XX可選數字,越大壓縮比率越高,但也越失真,
可以用如下cmd指令來壓縮:
gifsicle -O3 froSrc.gif --lossy=30 -o toSrc.gif

以下是Giflossy的資料

Giflossy
官網 : https://kornel.ski/lossygif
Github : https://github.com/pornel/giflossy


===================================================

Gifsicle和Giflossy的Github上都沒有提供給Windows現成的 exe 檔,
但有提供Makefile,可以利用Visual Studio 內建命令列視窗中的 make 工具來進行
編譯,
下載好Gifsicle或Giflossy後,如果有裝Visual Studio,可以打開"開發人員命令提示字元" ( Developer Command Prompt ),進到 gifsicle/src/ 內,打上
nmake -f Makefile.w32

就可以在當下路徑中看到編譯出來的 gifsicle.exe 了,
要注意的是它並沒有幫忙編譯 gifdiff.exe 。

如果有人沒有 Visual Studio,或是需要32位元的編譯版本,
我在這裡發現有人幫忙編譯好了 ,順便也放在這裡分享。


2017年5月29日 星期一

AngularJS的UI-Router練習

今天要來練習AngularJS很多人使用的外掛,UI-Router,UI-Router以各種不同的 State (狀態) 來管理AngularJS的路由,擴充了AngularJS原生路由的使用功能 (只單純以Url來組織路由),所以網站是以一個State到另一個State的方式去設計。

以下是今天演示的需求:

  1. 設計三個State, home、heroPanel、heroDetail,
    home為首頁,heroPanel顯示英雄(hero)的 id 和 name 的列表,點列表的某一個 hero 會進到heroDetail,顯示 Detail 頁面。
  2. 頁面結構分成三個區塊,header、body和footer,三者的內容皆會跟據State而有所改變。

成品就像下面影片這樣:




首先先來看一下設計的檔案結構:

index.html為主要頁面,且此例也只有一個頁面,並在同一頁中利用UI-Router切換內容。

因為這邊我用npm的方式下載安裝AngularJS和UI-Router,所以有package.json,AngularJS和UI-Router都裝在node_modules裡,當然自己去官網下載也OK。

app資料夾下的為主要JS程式,第一層以app開頭的JS為主要AngularJS Module及其相關設訂,代表著 "home" 的狀態。
其他的資料夾, heroPanel 和 heroDetail 代表 heroPanel 和 heroDetail 兩個狀態,為ui-view是body時的內容設定。
common  資料夾下放著跟 footer 和 header 兩個 ui-view相關的設定

main.css 為 header、 body、footer 標上外框以利識別,也為 class="active" 的元素標上底色以利識別現在狀態。

接下來來看各檔案裡的程式內容

2017年5月21日 星期日

Typescript + Webpack (or SystemJs) 練習

今天要來練習使用Typescript編寫Javascript,順便紀錄下如果JS Module的使用方式。

Typescript可以讓我們編寫的 ts 檔編譯成 js 檔,而如果有在 ts 檔裡撰寫Module的語法,
例如export, import等語句,Typescript可以依我們指定的module spec 來將 ts 檔編譯成不同
寫法的 js 檔,而可提供的有例如 amd, system, commonjs 等。

這裡會練習以下項目;

模組化 JS 執行工具:
SystemJs是一個可以執行JS Module的工具,
支援許多module spec,目前有
esm (ECMAScript Module),
cjs (CommonJS),
amd (Asynchronous Module Definition),
global (Global shim module format),
system (System.register or System.registerDynamic compatibility module format),
可以使用在node.js及Web應用中,在Web應用中,
可以在頁面上真接用 <script src="XXX/system.js"></script> 引入,
然後直接寫Javascript code 並指定程式進入點即可。

Webpack是一個功能強大的模組化打包工具,可以將多個資源(JS, CSS, images...)打包成一個檔案,可以解析例如commonjs的module spec,主要以命令列的方式使用,也可搭配其他外掛使用。

在這邊我們會先練習利用Typescript編譯 ts 檔,並且練習兩種不同的 module spec ,
system 和 commonjs, Typescript 對不同的 module spec 編譯出不同的 js 檔,
然後
system 我們會利用 SystemJs 來執行模組化的 js。
commonjs 我們會利用 Webpack 來打包編譯好的 js 檔成一個可執行的模組化 js。

Typescript IDE :
在這邊,可以準備一個有 Typescript 功能的 IDE,例如在這邊我使用 Visual Studio Code ,它有不錯的 Typescript 提供檢查功能及許多外掛,並且本身乾淨可以當一般的編輯器使用,當然也可以根據開發環境使用想應的工具外掛,例如 Netbeans 及 Eclipse (可以參考"Angular2簡易安裝使用 - Eclipse + Typescript plugin + System.js") 也有人提供Typescript外掛,享受使用 Typescript 的好處。
** Note **
Visual Studio Code 如果看不到JS、Typescript的提示文字,可以用"系統管理員"的方式開啟,應該就會出來了。

Typescript 的編譯:
在這邊為了了解最源頭的運作,我選擇使用最單純的方式,用 npm 安裝了 Typescript 後,直接用 tsc 指令配合 tsconfig.json 來執行編譯,許多開發環境的外掛,例如 Visual Studio Code 自己就有編譯Typescript的能力,不過其實最底層也都基本是呼叫 tsc 指令來完成工作。

簡易Server:
這邊使用了 lite-server 來在專案當下目錄建立簡易Server,因為Module JS的寫法會用到Ajax的呼叫,所以需要建立Server,當然要用其他例如webpack-dev-server、Netbeans, Eclipse 配合 Tomcat 之類的也是OK。

=========================================

首先,請先安裝 node.js (自動有 npm),
在全局安裝 Typescript、Webpack
npm install typescript -g
npm install webpack -g

2017年5月14日 星期日

將網頁要引入的前端套件Code再到網頁中 - bower + gulp + wiredep + gulp-inject


bower可以很方便的下載安裝前端套件(JS、CSS等),而這些下載下來的套件可以利用
gulp-inject及wiredep配合gulp來執行自動化工作,把要用的套件引入程式碼加到網頁檔案的(html, jsp等)。

以下提供一個使用的範例,用到的有(當然要先裝node.js):
  1. bower : 用來管理前端套件(JS、CSS等)。
  2. gulp :用來執行自動化工作(task)。
  3. gulp-inject :gulp的外掛之一,可以用來將JS、CSS的引入Code寫到網頁檔案中。
    例如html中的 <script src=“xxx.js"></script>
  4. wiredep:跟gulp-inject一樣,不過是用來將bower安裝的套件引入Code寫到網頁當案中,會自動偵測被引入套件中的bower.json來獲知要引入的JS、CSS等,可以直接寫Code用node.js執行,也有提供gulp的使用方式。
安裝node.js後,進行以下步驟:

  1. 首先先來安裝bower、gulp到命令列模式打上指令進行全域安裝。
    node install bower -g
    node install gulp -g
  2. 接著再到已建立的專案的專案路徑下,用以下指令建立一個package.json來管理此專案用node.js安裝的套件(之後可以直接用node install安裝回全部要的套件)。
    node init
  3. 接著再到已建立的專案的專案路徑下執行gulp、gulp-inject、wiredep的安裝(非全域,只是當下專案會用到,但因為之後要寫require("gulp")的語法,所以gulp要在專案下再安裝一次),使用--save-dev來寫入到package.json的devDependencies(表示開發用)中。
    node install gulp --save-dev
    node install gulp-inject --save-dev
    node install wiredep --save-dev
  4. 使用以下指令建立一個bower.json來管理此專案用bower安裝的套件(之後可以直接用bower install安裝回全部要的套件)。
    bower init
  5. 接著使用bower來安裝前端套件,例如angularJS、angular-fancy-modal
    bower install angular --save
    bower install angular-fancy-modal --save
    安裝好的套件預設會被放到bower_components資料夾中。
  6. 建立一個網頁(index.html)、一個JS檔案、一個CSS檔案做測試,
    然後再建立一個檔名為gulpfile.js的檔案,裡面會寫上gulp的自動化工作,
    現在的檔案結構應該會例如像下面這樣:

  7. 先在index.html寫下以下程式碼,其中,
    bower:css、bower:js和endbower是用來告訴wiredep要在哪加進JS、CSS引入程式碼。
    inject:css、inject:js和endinject是用來告訴gulp-inject要在哪加進JS、CSS引入程式碼。
    <!DOCTYPE html>
    <html>
        <head>
            <title>TODO supply a title</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            
            <!-- bower:css -->
            <!-- endbower -->
            
            <!-- bower:js -->
            <!-- endbower -->
            
            <!-- inject:css -->
            <!-- endinject -->
            
            <!-- inject:js -->
            <!-- endinject -->
        </head>
        <body>
            <div>TODO write content</div>
        </body>
    </html>
  8. 接著在gulpfile.js裡寫下如下程式碼:
    var gulp = require("gulp");
    var wiredep = require("wiredep").stream;  //wiredep提供給gulp使用的方式,使用stream來讓gulp能使用wiredep
    var gulpInjct = require("gulp-inject");
    
    //設定gulp工作(task)
    gulp.task("injectScriptCssTask", function () {
      //用wiredep來在index.html加入從bower install來的JS、CSS套件
      //Use wiredep to inject script, css installed by bower.
      gulp.src("index.html").pipe(wiredep({
                                optional : "configuration",
                                goes : "here"
                             }))    
      //用gulp-inject來在index.html加入自已指定的JS、CSS套件
      //Use gulp-inject to inject our own script, css.  
                            .pipe(gulpInjct(gulp.src(['./app/**/*.js', './app/**/*.css'],
                                                     {read : false}  //因為不用讀取JS、CSS檔的內容,只是想知道它們的path,
                                                                    //所以設定 {read : false} 增進效能
                                                    ),
                                            {relative : true}))  //設置{relative : true}指定用相對如徑的方式來寫入引人程式碼 
                            .pipe(gulp.dest("."));
    });
  9. 最後使用命令列視窗,到專案目錄執行gulp的工作(此例工作名稱為injectScriptCssTask):
    gulp injectScriptCssTask
    再到index.html中,應該就可以看到JS、CSS引入碼被成功地寫入到檔案中了:
    <!DOCTYPE html>
    <html>
        <head>
            <title>TODO supply a title</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            
            <!-- bower:css -->
            <link rel="stylesheet" href="bower_components/angular-fancy-modal/dist/angular-fancy-modal.css" />
            <!-- endbower -->
            
            <!-- bower:js -->
            <script src="bower_components/angular/angular.js"></script>
            <script src="bower_components/angular-fancy-modal/dist/angular-fancy-modal.js"></script>
            <!-- endbower -->
            
            <!-- inject:css -->
            <link rel="stylesheet" href="app/css/main.css">
            <!-- endinject -->
            
            <!-- inject:js -->
            <script src="app/js/app.js"></script>
            <!-- endinject -->
        </head>
        <body>
            <div>TODO write content</div>
        </body>
    </html>
原始碼下載:
AngularJsUI-RouterTest.7z

參考資料:
  1. gulp-inject
  2. wiredep
  3. Gulp 學習 2 - 打包壓縮 CSS 與 JS
  4. Gulp 學習 3 - 打包壓縮 HTML

2017年5月12日 星期五

AngularJS 自製有Service、directive的外掛module練習

今天要來製作一個自製的AngularJS外掛,用來練習AngularJS的模組化特性。

要實作的是一個簡單的Lightbox (或Modal),要練習的點是:

  1. 此外掛為一個Module,可引入至其他的AngularJS Module。
  2. 此Module提供一個Service,可以由引入此Module的程式呼叫使用。
  3. 此Module提供一個Directive,Directive可以與參數雙向(或單向,看需求)綁定,並且Directive可以呼叫上述Service來使用。

目標結果為:

  1. Lightbox外掛提供一個Service,可以開始及關閉Lightbox,並且可以在開啟Lightbox時提供要顯示在Lightbox中的文字。
  2. Lightbox外掛提供一個Directive,Directive經過設計,有提供一個按鈕,其被按下會執行Service的開啟lightbox功能,Directive可以給入參數,設定Lightbox中要顯示的文字。
  3. 開啟的Lightbox提供一個Close按鈕,按下可以關閉Lightbox。
  4. 一次只會有一個Lightbox被開啟。

其成品就像如下視頻那樣:


首先先 看一下檔案結構:


  1. /index.html:主要的網頁頁面。
  2. /js/index.js :index.html使用的JS,主要頁面的AngularJS邏輯。
  3. /js/angular.js : 要引入使用的angularjs。
  4. /js/myAngularJsPlugin.js : 要撰寫的自製Lightbox AngularJS Plugin。
程式碼如下,相關的說明都已經寫在註解中了:
  1. /index.html :
    <!DOCTYPE html>
    <html>
        <head>
            <title>TODO supply a title</title>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
            <script src="js/angular.js"></script>
            <script src="js/myAngularJsPlugin/myModalModule.js"></script>
            <script src="js/index.js"></script>
        </head>
        <body>
            <div ng-app="myApp" ng-controller="myController as myCtrl">
                可自行輸入要在Modal內顯示的文字
                <div style="margin-bottom: 20px;">
                    <input type="text" ng-model="myCtrl.modalText"/>
                </div>
                在Controller裡呼叫自製Module提供的Service來開啟Modal
                <div style="margin-bottom: 20px;">                
                    <button ng-click="myCtrl.openModal(myCtrl.modalText)">Open Modal manually</button>
                </div>
                
                使用自製Module提供的Directive來開啟Modal
                <div>                
                    <my-modal-directive modal-text="myCtrl.modalText"></my-modal-directive>
                </div>
            </div>
        </body>
    </html>
  2. /js/index.js :
    angular.module("myApp", ["myModalModule"]);
    
    angular.module("myApp").controller("myController", ["myModalService", function(myModalService){
        var self = this;
        self.modalText = "測試文字";
        self.openModal = openModal;
        
        function openModal(modalText){
            myModalService.open(modalText);
        }
    }]);
  3. /js/myAngularJsPlugin.js:
    //Module
    angular.module("myModalModule", []);
    //Service
    angular.module("myModalModule").factory("myModalService", ["$rootScope", "$compile", function ($rootScope, $compile) {
            //設計一次只能有一個Modal開啟
            var scope;
            var modal;
            
            var $body = angular.element(document).find('body');
            var isModalOpened = false;
            //modal template中有跟angularjs scope有關的程式碼,
            //須要使用$compile編譯並與scope連結才能有作用
            var modalTemplate = '<div style="position: fixed; top: 0px; left: 0px; width: 100%; height: 100%; background-color: rgba(158, 158, 158, 0.68); ">' +
                                    '<button ng-click="close()" >Close</button>' +
                                    '<div style="display: flex; align-items: center; justify-content:center; width: 100%; height: 100%;">' +
                                        '{{textContent}}' +
                                    '</div>' +
                                '</div>';
            //functions
            function open(textContent) {
                if (!isModalOpened) {
                    //創建暫時的scope
                    scope = $rootScope.$new();
                    //在scope中設置close函式
                    scope.close = close;
                    //在scope中設置textContent
                    scope.textContent = textContent;
                    //使用$compile來編譯modalTemplate,得到link函式,執行link函式並代入scope來與scope連結,
                    //最後得到連結好的DOM
                    modal = $compile(modalTemplate)(scope);
                    //將得到的DOM加進<body>中
                    $body.append(modal);
                    //設置modal opened狀態
                    isModalOpened = true;
                }
    
            }
            function close() {
                if (isModalOpened) {
                    //將Modal DOM移除
                    modal.remove();
                    //移除暫時scope
                    scope.$destroy();
                    //設置modal opened狀態
                    isModalOpened = false;
                }
            }
            //自製Service對外提供的API
            return {
                open: open,
                close: close
            };
        }]);
    //Directive
    angular.module("myModalModule").directive("myModalDirective", ["myModalService", function (myModalService) {
            return {
                restrict: "E",
                replace: true,
                scope: {
                    //自製Directive可接受modalText參數(為表達示)
                    modalText: "=modalText"
                },
                template: generateTemplate,
                link: function ($scope, $elm, $attrs, $ngModel) {
                    $scope.openModal = myModalService.open;                
                    /* 以上寫法等同以下,上面雖簡潔但下面寫法可讀性比較好
                    $scope.openModal = function (modalText) {
                        myModalService.open(modalText);
                    }
                    */
                }
            };
    
            function generateTemplate($elm, $attrs) {
                 //用function回傳template,也可以直接用字串不用函式
                return '<button ng-click="openModal(modalText)">Open Modal by Directive</button>';
            }
        }]);
原始碼下載:
angularJS-servicePractice.7z


參考資料:
  1. 深入解析 CSS Flexbox