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