Mars's Blog

CRUD表單設計 Day-04 Javascript

一、說明

「互動式網頁」概念由來已久,但實作的的方式一直不斷更新,如javascript, Java applet, flash, css等。其中Javscript支援度高、應用廣、學習容易,很不錯的語言。

  • 在此透過jQuery來使用Javascript, AJAX
  • 本文中採用Google Chrome瀏覽器及其開發人員工具

二、主要應用

  • 選擇器(Selector)
  • DOM控制
    • 結構
    • 事件
  • 資料處理
  • AJAX

三、練習模版

3.1 增加路徑常數

  • 打開檔案
    application/config/constants.php

  • 加入設定

    1
    2
    3
    4
    5
    6
    /**
    * System Path
    */
    define('ASSETS', '/assets/');
    define('CSS_DIR', ASSETS . 'css/');
    define('JS_DIR', ASSETS . 'js/');
  • 建立資料夾
    assets/js/
    assets/css/

3.2 建立Controller

  • 新增檔案
    application/controllers/Js_training.php

  • 寫入內容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php
    defined('BASEPATH') or exit('No direct script access allowed');

    class Js_training extends CI_Controller
    {

    /**
    * Index Page for this controller.
    */
    public function index()
    {
    $this->load->view('jsTraining');
    }
    }

3.3 建立View

  • 新增檔案
    application/views/jsTraining.php

    以此為HTML練習模版

  • 寫入內容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <?php
    defined('BASEPATH') or exit('No direct script access allowed');
    ?>
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <title>Bootstrap 4 Example</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.min.css">
    <script src="/node_modules/jquery/dist/jquery.min.js"></script>
    <script src="/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
    <!-- Font Awesome Icon -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css" />

    <script src="<?= JS_DIR; ?>jsTraining/jsTraining.js"></script>
    </head>

    <body>

    <div class="container">
    <!-- Title -->
    <div class="row">
    <div class="col-sm-12">
    <h1>Javascript Training</h1>
    <hr>
    </div>
    </div>

    <!-- Controll Form -->
    <div class="row">
    <div class="col-sm-12"><button class="btn btn-warning float-right ctrl-btn">新增</button></div>
    </div>

    <!-- Table -->
    <div class="row">
    <table class="table table-striped table-bordered table-hover ctrl-table">
    </table>
    </div>

    <!-- Message box -->
    <div class="row">
    <div id="ctrl-message" class="text-danger ctrl-message"></div>
    </div>

    </div>

    </body>

    </html>

3.4 建立JS檔

  • 新增檔案
    assets/js/jsTraining/jsTraining.js

  • 寫入內容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    /**
    * 說明:
    * <li>1. 頁面函式只會初始化一次
    * <li>2. 如果是多頁面組合時,可能被其他頁面呼叫,因此需使用namespane:Page,以方便外部呼叫或試調
    *
    * 執行順序:
    * 1. 註冊$(document).ready()函式,但先不執行
    * 2. $(document).ready()之外的程式碼依序執行 - 建構變數、函式obj
    * 3. 執行$(document).ready()內註冊的函式
    * 4. 確定window.Page是否存在,不存在則初始化
    * 5. 執行obj()物件,並將結果存入window.Page[name]
    * 6. obj()回傳內容為 new obj.fn.init(options);
    * 7. 實例化obj.fn.init(options);並在最後執行函式 _construct(_options);
    */

    // IIFE 立即執行函式
    (function(window, document, $, undefined) {
    // 使用嚴格模式
    'use strict';

    // DOM下載完後執行
    $(document).ready(function() {
    // init this page
    window.Page = window.Page || new function() {}();
    window.Page[name] = obj();
    });

    // Class Name
    var name = '{name}';
    // Version
    var version = '{version}';
    // Default options
    var defaults = {};

    /**
    * *************** Object Build ***************
    */

    // Define a local copy of Object
    var obj = function(options) {
    return new obj.fn.init(options);
    };

    // Prototype arguments
    obj.fn = obj.prototype = {
    // Object Name
    _name: name,

    // Default options
    _defaults: defaults,

    // AJAX URL
    _ajaxUrls: {
    list: '/controller_group_forder/controller/ajax',
    edit: '/controller_group_forder/controller_edit/ajax',
    },
    };

    /**
    * Javascript物件
    */
    obj.fn.init = function(options) {
    /**
    * *************** Object Argument Setting ***************
    */
    var self = this;
    var _options = options || {};
    // Ajax Response - jqXHR(s)
    var _jqXHRs;

    /**
    * *************** 屬性設定 ***************
    */

    /**
    * *************** 物件必要函式 ***************
    */

    /**
    * 建構子
    */
    var _construct = function() {
    console.log('_construct');

    _initialize();
    };

    /**
    * 解構子
    */
    var _destruct = function() {};

    /**
    * 初始化
    */
    var _initialize = function() {
    console.log('_initialize');

    /**
    * 事件綁定
    */
    _evenBind();
    };

    /**
    * 事件綁定
    */
    var _evenBind = function() {
    console.log('_evenBind');

    /**
    * 事件 - 增加
    */

    /**
    * 事件 - 清除規
    */
    };

    /**
    * *************** 功能函式 ***************
    */

    /**
    * *************** 事件函式 ***************
    */

    /**
    * 事件 - 送出
    */
    var _submit = function(e) {
    return this;
    };

    /**
    * 事件 - 清除
    */
    var _clear = function(e) {
    return this;
    };

    /**
    * 事件 - 增加
    */
    var _add = function(e) {
    return this;
    };

    /**
    * *************** 私有函式 ***************
    */

    /**
    * *************** Run Constructor ***************
    */
    _construct();
    };

    // Give the init function the Object prototype for later instantiation
    obj.fn.init.prototype = obj.prototype;

    // Alias prototype function
    $.extend(obj, obj.fn);
    })(window, document, $);

    物件化Javascript模版

3.5 查看結果

四、選擇器(jQuery Selector)

使用tag, id, class取得DOM,並讀取物件內容

4.1 使用tag selector

讀取tag:h1內容字串
在函式 _initialize 中寫入下列程式碼後,按ctrl+F5重整網頁

1
2
3
4
5
6
// 讀取tag:h1內容字串
var text = $('h1').text();
// 在DevTools的console介面中印出
console.log('----');
console.log('tag:h1的內容為:' + text);
console.log('----');
  • 按F12打開「開發人員工具」,移到console頁籤
  • JS翻釋:使用tag選擇器尋找名為h1的元件,並取得元件內容字串
  • tag為HTML標籤名稱,參考文件 HTML5 Tutorial

4.2 使用id selector

寫入「測試字串」至id=”ctrl-message”的元件
在函式 _initialize 中寫入下列程式碼後,按ctrl+F5重整網頁

1
2
// 寫入「測試字串」至id="ctrl-message"的元件
$('#ctrl-message').text('測試字串');
  • JS翻釋:使用ID選擇器(#)尋找id為ctrl-message的元件,並將字串「測試字串」寫入元件內容

4.3 使用class selector

變更class=”ctrl-btn”按鈕的元件的顏色為btn-danger
在函式 _initialize 中寫入下列程式碼後,按ctrl+F5重整網頁

1
2
// 變更class="ctrl-btn"按鈕的元件的顏色為btn-danger
$('.ctrl-btn').removeClass('btn-warning').addClass('btn-danger');
  • JS翻釋:使用class選擇器(.)尋找含有class為ctrl-btn的元件,並移除class btn-warning、增加class btn-danger

4.4 查看結果

五、DOM控制

5.1 結構

5.1.1 移除按鈕

在函式 _initialize 中寫入下列程式碼後,按ctrl+F5重整網頁

1
2
// 移除按鈕
$('.ctrl-btn').remove();
  • JS翻釋:使用class選擇器(.)尋找含有class為ctrl-btn的元件,並移除該元件

5.1.2 建立表格

在函式 _initialize 中寫入下列程式碼後,按ctrl+F5重整網頁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 建立表格
*/
// 建立變數
var tmp, table, thead, tbody, tr, th, td;
// 建立暫存容器
tmp = $('<div></div>');
// 建立thead區塊資料,並放到tmp之中
thead = $('<thead></thead>').appendTo(tmp);
// 建立tr標籤,並放到thead之中
tr = $('<tr></tr>').appendTo(thead);
// 建立th標籤,並放到tr之中
th = $('<th></th>').appendTo(tr);
// 寫入th內容文字
th.text('Count');
th = $('<th></th>').appendTo(tr);
th.text('Name');
th = $('<th>Gender</th>').appendTo(tr);

// 建立tbody區塊資料
tbody = $('<tbody></tbody>').appendTo(tmp);
tr = $('<tr></tr>').appendTo(tbody);
td = $('<td></td>').appendTo(tr);
td.text('1');
td = $('<td></td>').appendTo(tr);
td.text('Joe');
td = $('<td>male</td>').appendTo(tr);

// 取得table元件
table = $('.ctrl-table');
// 將暫存容器內容移至table元件
tmp.children().appendTo(table);

// 印出table元件HTML碼
console.log(table.html());

參考文件:jQuery Add, jQuery Remove

5.2 事件

5.2.1 按下按鈕時,變更tag:h1文字為「** Javascript Training **」

在函式 _evenBind 中寫入下列程式碼後,按ctrl+F5重整網頁

1
2
3
4
// 按下按鈕時,變更tag:h1文字為「** Javascript Training **」
$('.ctrl-btn').on('click', function() {
$('h1').text('** Javascript Training **');
});
  • JS翻釋:
    • 當class為ctrl-btn的元件,在(on)按下click時,執行匿名函式
    • 匿名函式:對tag為h1的元件內容改寫為「** Javascript Training **」

5.2.2 偵測滑鼠座標

在函式 _evenBind 中寫入下列程式碼後,按ctrl+F5重整網頁

1
2
3
4
5
6
7
8
9
10
// 偵測滑鼠座標
$(document).on('mousemove', function(ev) {
// 取得事件物件
ev = ev || window.event;
// 如果事件物件含有座標資訊
if (ev.pageX || ev.pageY) {
// 在ctrl-message元件中印出座標資訊
$('.ctrl-message').html('X: ' + ev.pageX + '<br>Y: ' + ev.pageY);
}
});
  • JS翻釋:
    • 當在整個文件區(document)中偵測到滑鼠移動事件(mousemove)時,執行匿名函式
    • 匿名函式:取得事件物件,如果事件物件含有座標資訊,在ctrl-message元件中印出座標資訊

參考文件:jQuery Events

5.3 查看結果

六、AJAX

AJAX即「Asynchronous JavaScript and XML」(非同步的JavaScript與XML技術),使用AJAX執行前後端資料交換有助於減少資料量,非同步特性讓ajax處理時,可同時處理本其他JS程式,讓網頁執行更順暢更多樣化。

AJAX的目的可粗略分為建立(create)、讀取(read)、更新(update)、刪除(delete)。

在此使用RESTFul Style實作ajax前後端資料交換

6.1 修改AJAX server side相關路徑

編輯檔案:assets/js/jsTraining/jsTraining.js

1
2
3
4
5
// AJAX URL
_ajaxUrls: {
// Account CRUD AJAX server side url.
accountApi: '/js_training/ajax',
},

6.2 建立伺服器端接收函式

檔案:application/controllers/Js_training.php
增加下列函式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* AJAX controller.
*/
public function ajax($id = null)
{
// 參數處理
$method = strtoupper($_SERVER['REQUEST_METHOD']);
// 此處應有對傳入參數$_POST消毒的處理,此處簡化
//parse_str(file_get_contents('php://input'), $data);
$data = $this->input->input_stream();

// 行為分類
switch ($method) {
case 'POST':
// 新增一筆資料
$this->_create($data);
break;
case 'GET':
if (empty($id)) {
// 讀取全部資料
$this->_list();
} else {
// 讀取一筆資料
$this->_read($id);
}
break;
case 'PATCH':
case 'PUT':
// 更新一筆資料
$this->_update($data, $id);
break;
case 'DELETE':
if (empty($id)) {
// 錯誤
http_response_code(404);
echo 'No Delete ID';
exit;
} else {
// 刪除一筆資料
$this->_delete($id);
}
break;
}
}

/**
* 新增一筆
*
* @param array $data
* @return array
*/
protected function _create($data)
{
// 建立輸出陣列
$opt = [
// 行為:新增一筆
'type' => '新增一筆',
// 前端AJAX傳過來的資料
'data' => $data,
];

// 輸出JSON
echo json_encode($opt);
}

/**
* 讀取全部
*
* @return array
*/
protected function _list()
{
// 建立輸出陣列
$opt = [
// 行為:讀取全部
'type' => '讀取全部',
// 標題資料
'head' => [
'name',
'location',
],
// 多筆內容資料
'data' => [
[
'name' => 'John',
'location' => 'Boston',
],
[
'name' => 'Joe',
'location' => 'New York',
],
[
'name' => 'Gary',
'location' => 'Taipei',
],
],
];

// 輸出JSON
echo json_encode($opt);
}

/**
* 讀取一筆
*
* @param int $id 目標資料id
* @return array
*/
protected function _read($id)
{
// 建立輸出陣列
$opt = [
// 行為:讀取一筆
'type' => '讀取一筆',
// 前端AJAX傳過來的資料
'id' => $id,
];

// 輸出JSON
echo json_encode($opt);
}

/**
* 更新一筆
*
* @param array $data 資料內容
* @param int $id 目標資料id
* @return array
*/
protected function _update($data, $id)
{
// 建立輸出陣列
$opt = [
// 行為:更新一筆
'type' => '更新一筆',
// 前端AJAX傳過來的資料
'data' => $data,
'id' => $id,
];

// 輸出JSON
echo json_encode($opt);
}

/**
* 刪除一筆
*
* @param int $id 目標資料id
* @return string
*/
protected function _delete($id)
{
// 建立輸出陣列
$opt = [
// 行為:刪除一筆
'type' => '刪除一筆',
// 前端AJAX傳過來的資料
'id' => $id,
];

// 輸出JSON
echo json_encode($opt);
}

6.3 Create

6.3.1 程式碼

  • 新增一筆
    在函式 _initialize 中寫入下列程式碼後,按ctrl+F5重整網頁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 新增一筆
    */
    $.ajax({
    // 傳送方法
    method: 'POST',
    // 目標網址
    url: self._ajaxUrls.accountApi,
    // 傳送資料
    data: { name: 'John', location: 'Boston' },
    // 回傳資料格式
    dataType: 'json',
    }).done(function(data) {
    // 處理回傳資料 - 印出json字串
    $('<div>' + JSON.stringify(data) + '</div>').appendTo($('.ctrl-message'));
    // 輸出至console
    console.log(data);
    }).fail(function (jqXHR) {
    // 處理回傳資料
    $('<div>' + jqXHR.responseText + '</div>').appendTo($('.ctrl-message'));
    console.log(jqXHR);
    });

    示範AJAX Request & Reponse

6.3.2查看結果

6.4 Read

  • 讀取全部

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    /**
    * 讀取全部
    */
    $.ajax({
    method: 'GET',
    url: self._ajaxUrls.accountApi,
    dataType: 'json',
    }).done(function(data) {
    // 處理回傳資料 - 印出json字串
    $('<div>' + JSON.stringify(data) + '</div>').appendTo($('.ctrl-message'));

    /**
    * 陣列資料配合$.each建立表格
    */
    // 建立變數
    var tmp, table, thead, tbody, tr, th, td;
    // 建立暫存容器
    tmp = $('<div></div>');
    // 建立thead區塊資料
    thead = $('<thead></thead>').appendTo(tmp);
    // 建立tbody區塊資料
    tbody = $('<tbody></tbody>').appendTo(tmp);

    // 建立標題
    tr = $('<tr class="bg-info"></tr>').appendTo(thead);
    $.each(data.head, function(index, value) {
    th = $('<th>'+value+'</th>').appendTo(tr);
    });

    // 建立內容
    $.each(data.data, function(index1, value1) {
    tr = $('<tr></tr>').appendTo(tbody);
    $.each(value1, function(index2, value2) {
    td = $('<td>'+value2+'</td>').appendTo(tr);
    });
    });

    // 取得table元件
    table = $('.ctrl-table');
    // 將暫存容器內容移至table元件
    tmp.children().appendTo(table);
    });

    陣列資料配合$.each建立表格

  • 讀取一筆

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 讀取一筆
    */
    $.ajax({
    method: 'GET',
    // 讀取id為3的資料
    url: self._ajaxUrls.accountApi + '/3',
    dataType: 'json',
    }).done(function(data) {
    // 處理回傳資料
    $('<div>' + JSON.stringify(data) + '</div>').appendTo($('.ctrl-message'));
    });

參考文件:$.each()

6.5 Update

  • 更新一筆
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 更新一筆
    */
    $.ajax({
    method: 'PUT',
    url: self._ajaxUrls.accountApi,
    data: { name: 'John', location: 'Boston' },
    dataType: 'json',
    }).done(function(data) {
    // 處理回傳資料
    $('<div>' + JSON.stringify(data) + '</div>').appendTo($('.ctrl-message'));
    });

6.6 Delete

  • 刪除錯誤 No Delete ID

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 刪除錯誤 No Delete ID
    */
    $.ajax({
    method: 'DELETE',
    url: self._ajaxUrls.accountApi,
    dataType: 'json',
    }).done(function(data) {
    // 處理回傳資料
    $('<div>' + JSON.stringify(data) + '</div>').appendTo($('.ctrl-message'));
    }).fail(function (jqXHR) {
    // 錯誤處理
    $('<div>' + jqXHR.responseText + '</div>').appendTo($('.ctrl-message'));
    console.log(jqXHR);
    });
  • 刪除一筆

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * 刪除一筆
    */
    $.ajax({
    method: 'DELETE',
    // 刪除id為2的資料
    url: self._ajaxUrls.accountApi + '/2',
    dataType: 'json',
    }).done(function(data) {
    // 處理回傳資料
    $('<div>' + JSON.stringify(data) + '</div>').appendTo($('.ctrl-message'));
    }).fail(function (jqXHR) {
    // 錯誤處理
    $('<div>' + jqXHR.responseText + '</div>').appendTo($('.ctrl-message'));
    console.log(jqXHR);
    });

參考文件:

6.7 資料交換要點

  • 使用方法POST,GET,PUT,DELETE分別對應新增,讀取,修改,刪除
  • 網址格式為:https://host_name/controller_name/function_name/parameter
  • 回傳的資料格式為json
  • POST,PUT需傳入data屬性,值為要新增/更新的資料
  • 使用callback函式done()處理成功回傳的資料
  • 使用callback函式fail()處理失敗回傳的資料
  • 需定義好從 前端傳到後端 與 後端傳到前端 的資料結構,以免資料處理雜亂

七、參考

7.1 知識

7.2 官網

7.3 教程


未完待續