2019年12月29日 星期日


在使用 Angularjs 的 directive 時,
有時我們會想要在外層 (即使用 directive 的 controller 或其他 directive) 去呼叫 directive 裡面的 function,
去改變一些 directive 的內部狀態,
這時使用 service 就是一個比較好的做好,
service 可以作為 directive 及使用 directive 的元件 (例如外部 controller 或其他 directive)
的溝通橋梁,
利用把 directive 的 scope 或 內部 controller 綁定設定到 service 中,
我們即可在外部將 service 注入,並使用 service 來控制 directive。

下面給出一個範例 : 可以看到,<my-form-directive> 是我們實作的一個 directive,
裡面有一個名為 "pristineForm" 的 function ,
其可以設定 $scope 裡面的 myForm (為在 directive template 中 name="myForm" 的 <form>)
成初始狀態 ( 使用 formController.$setPristine() )。

在 <my-form-directive> 外,有一個 <div> 被設定了  ng-click="ctrl.pristineForm() ,
會去呼叫外層 controller 的 pristineForm(),
而 pristineForm() 會利用引入的 directiveHandler 這個 service ,
去呼叫 myFormDirective 這個 directive 內部的 pristineForm()。

下面是詳細的程式碼:

Javascript :
angular.module("app", [])
  .controller("controller", ["directiveHandler", function(directiveHandler) {
    var self = this;
    self.pristineForm = function(directiveName) {
     var directiveName = directiveName ? directiveName : 'myFormDirective';
      directiveHandler.getDirective(directiveName).pristineForm();
    }

  }])
  .directive("myFormDirective", ["directiveHandler", function(directiveHandler) {
    return {
      restrict: "E",
      template: `<div>
                  <form name="myForm">
                    Username : <input type="text" name="username" ng-model="username" required/>
                    <div ng-show="myForm.username.$dirty && myForm.username.$invalid">
                      Please fill in username.
                    </div>
                    <div>Form $pristine : {{myForm.$pristine}}</div>
                  </form>
            </div>`,
      replace: true,
      scope: {
      },
      link: function($scope, $elm, $attrs, $ctrl) {
        var directiveNameAttr = $attrs.dirName ? $attrs.dirName : "myFormDirective";
        directiveHandler.registerDirective(directiveNameAttr, $scope);

        $scope.username = "";
        $scope.pristineForm = function() {
          $scope.myForm.$setPristine();
        }
      }
    };
  }]).factory('directiveHandler', function() {
    var instance_map = {};
    var service = {
      registerDirective: registerDirective,
      getDirective: getDirective,
      deregisterDirective: deregisterDirective
    };

    return service;

    function registerDirective(name, ctrl) {
      instance_map[name] = ctrl;
    }

    function getDirective(name) {
      return instance_map[name];
    }

    function deregisterDirective(name) {
      instance_map[name] = null;
    }
  });

HTML :
<div ng-app="app" ng-controller="controller as ctrl">
  <my-form-directive dir-name="myFormDir"></my-form-directive>
  <div ng-click="ctrl.pristineForm('myFormDir')"><button>Set Form Pristine</button></div>
  
  <my-form-directive dir-name="myFormDir2"></my-form-directive>
  <div ng-click="ctrl.pristineForm('myFormDir2')"><button>Set Form Pristine</button></div>
</div>



說明:
在 directiveHandler 中,設定了一個內部管理的 instance_map,
用 key-value 的次式將需要的 directive instance (可能是 isolate scope 的 scope,可能是 directive 設定的 controller 等,依情況自行決定) 存起來,
設定了三個 function,

主要的為  registerDirective(name, ctrl) 和 getDirective(name),
registerDirective(name, ctrl) 主要在 directive 初始化時使用一次,
用想要的 name 作為 key,把想要暴露給外部使用的 instance 作為 ctrl 設定進來。

getDirective(name) 在外部使用,例如此例的 controller,
用 name 值來取得 directive 的內部 instance,例如此例即為 <my-form-directive> 的 isolate scope,
取得 instance 後,即可執行 instance 上所設定的 function,當然取得 instance 上設定的值也是可以的。

在 html 中使用 <my-form-directive> 時,給了一個 dir-name 屬性,
dir-name 屬性在 directive 用來作為 name 參數呼叫 registerDirective(name, ctrl),
如果有多個 directive 的話,為了區分各個 directive,
給定不同的 name 作為 instance 在 directiveHandler 中的 key 值將會
使管理各個 directive 的 instance 更為方便。

為了演示,我們在 html 中使用了兩個 <my-form-directive>,並給它們設定了不同的 dir-anem 屬性值。

參考資料:

  1. How to call a method defined in an AngularJS directive? (最喜歡Mudassir Ali的回答)

2019年12月9日 星期一

RWD <table> - 純 html, css - 無 javascript

這篇紀錄了學到的 <table> RWD  的純 html, css 顯示技巧。
雖然在現今的 RWD 網站設計裡,因為 <table>的一些缺點,
通常都還是建議以不要用 <table> 的方式去做設計 (改用 <div>....等)。

但有時我們還是會有需要用到 <table> 的情況,
例如:

  1. 因為某些原因難以重改 html 結構 (含有 <table> ) 的網站 (老專案、時程、人力、需求等 ......)。
  2. <table> 在表示列表型資料時的方便性,居中對齊、控制寬度容 (雖然通常代表寬度 無法 RWD) 易等。

如果真得要用 <table> 時,有辦法加上一些 RWD 的效果嗎?
以下為 <table> RWD CSS 的範例,先看在 JSFiddle 的成果展示:



如果是寬螢幕的話,會是如下顯示:



如果是窄螢幕的話,就會是如下顯示:



再來看 html 結構及 CSS:

 html :
<table class="rwd-table">
  <tr>
    <th></th>
    <th>Very poor</th>
    <th>Poor</th>
    <th>Fair</th>
    <th>Good</th>
    <th>Very good</th>
  </tr>
  <tr>
    <td data-th="">Score 1 :</td>
    <td data-th="Very poor"><input type="radio" name="Score1"/></td>
    <td data-th="Poor"><input type="radio" name="Score1"/></td>
    <td data-th="Fair"><input type="radio" name="Score1"/></td>
    <td data-th="Good"><input type="radio" name="Score1"/></td>
    <td data-th="Very good"><input type="radio" name="Score1"/></td>
  </tr>
  <tr>
    <td data-th="">Score 1 :</td>
    <td data-th="Very poor"><input type="radio" name="Score2"/></td>
    <td data-th="Poor"><input type="radio" name="Score2"/></td>
    <td data-th="Fair"><input type="radio" name="Score2"/></td>
    <td data-th="Good"><input type="radio" name="Score2"/></td>
    <td data-th="Very good"><input type="radio" name="Score2"/></td>
  </tr>
  <tr>
    <td data-th="">Score 1 :</td>
    <td data-th="Very poor"><input type="radio" name="Score3"/></td>
    <td data-th="Poor"><input type="radio" name="Score3"/></td>
    <td data-th="Fair"><input type="radio" name="Score3"/></td>
    <td data-th="Good"><input type="radio" name="Score3"/></td>
    <td data-th="Very good"><input type="radio" name="Score3"/></td>
  </tr>
</table>

在 html 裡,只是放了一個普通的 <table> ,並在其加上了一個class, "rwd-table" 做為 rwd table 的 css selector,
另一個是我們用了 data-th (屬性名可自取) 這個屬性來記錄每個 <td> 對應的 <th> 標題值,
以利在窄螢幕時,可以用 css 的 :before 或 :after 偽元素中的 content 來取得標題值。

CSS:
/* normal table css */
/* normal table css */
table {
    border-collapse: collapse;
    border-spacing: 0;
}

table tr td, table tr th{
  border : 1px solid black;
}

table tr:nth-child(even){
  background-color : #d0aaaa;
}

/* rwd table css */
.rwd-table {
 overflow: hidden;
}

.rwd-table {
  min-width: 100%;
}

.rwd-table th {
  display: none;
}

.rwd-table td {
  display: block;
}

.rwd-table td:after {
  content: attr(data-th);
  display: inline-block;
}


.rwd-table tr {
    margin-bottom: 5px;
    display: inline-block;
    width: 100%;
}

@media (min-width: 583px) {
  .rwd-table td:after {
    display: none;
  }
 .rwd-table th, .rwd-table td {
    display: table-cell;
  }
  .rwd-table tr {
    margin-bottom: 0px;
    display: table-row;
  }
  
}

在 CSS 中,只要注意關於 .rwd-table 的部份 (即注解 /* rwd table css */ 以下的部份),
重點就是:

  1. 在寬螢幕時:
    1. th 正常顯示
    2. td:after 或 td:before 不顯示
    3. th, td 使用 display: table-cell
    4. tr 使用 display: table-row
  2. 在窄螢幕時:
    1. th 不顯示
    2. td:after 或 td:before 顯示,使用 content : attr(data-th) 全得 td 上的 data-th 屬性值
    3. td 使用 display: block
    4. tr 使用 display: inline-block


參考資料:
  1. Bootstrap教學-實現Table表格也支援RWD自適應效果
  2. 利用 Pure CSS 讓 HTML Table 也能有 RWD 效果 - Yowko's Notes
  3. 使用CSS製作響應式破壞式表格

2019年12月7日 星期六

React 練習 - ToDo List (待辦事項) 簡易實作

這篇要來記錄一下 React 的
ToDo List (待辦事項) 簡易實作(無用到 Redux 之類的)

首先先來看一下成果的樣子:

並將它分

接下來是需求說明:
  1. 有一個供使用者輸入的<input type="text"/> 框,使用者輸入待辦事項的文字後,
    按下Enter鍵(配合 <form>)或按下 "addToDo" 按鈕,可以加進下方的待辦事項顯示區域 (<ul><li>)。
    為了演示一下雙向資料流的實作,放了兩個待辦事項輸入框,
    在使用者在其中一個輸入框輸入資料時,另一個的輸入框會同步顯示輸入的待辦事項的內容。
  2. 使用者在輸入待辦事項的文字時,下方有一行文字可以顯示使用者正在輸入的文字。
  3. 下方的待辦事項清單中,各個待辦事項右方有一個 "Delete" 按鈕,按下可將待辦事項移除

再來把其需求分解成各個Component,如下圖所示:
  1. Main.jsx :
    統整所有 Component 的最上層 Component,也管理著 state。
  2. TextInput.jsx :
    供使用者輸入待辦事項的地方。
  3. TextDisplayer.jsx :
    顯示使用者正在輸入的待辦事項。
  4. ToDoList.jsx :
    顯示待辦事項例表的 Component,其內部用到了子 Component, "ToDoRow.jsx"。
  5. ToDoRow.jsx :
    顯示各個獨立待辦事項的 Component,包含著 Delete 等功能。