連接 DB 是一件必定要進行的事,在 Laravel 可以使用 ORM 去完成,而在 NodeJS 上,筆者用得最多是使用 npm mysql 去完成。雖然 NodeJS 上的 mysql library 是一個寫得很成熟的東西,但是在建立 query 時還是有很多不足的地方。最明顯的地方是在建立 conditional where 時。
### 例子
以下是一個壞的例子 :
```js
var sqlParams = [];
var where = [];
// conditional where case
if( typeof id === 'number' ) {
where.push('id = ?');
sqlParams.push(id)
}
// sql
var sql = 'select * from users where ' + where.join(' and ');
// execute query
db.query(sql, sqlParams, callback);
```
相信這樣的 SQL Statement 大家都有可能寫過 / 見識過。雖然上面的寫法還不致於十分混亂,但是讀起來也不是太方便。如果又要同時用上 AND 及 OR 時,可能就會出現比較混亂的情況了 !
### Knex.js - A SQL Query Builder for Javascript
這是 Knex.js 自己介紹自己的,它是一個 SQL Query Builder。
不過除此之後它還會自行管理 Connection 的生命週期,使用 Connection Pool 的話還會自行 release connection,算是一個非常便利及強勁的 Database adapter。
官方網站 : http://knexjs.org
以下是使用 Knex 的方法來重寫一次上面的代碼 :
```js
// query
var query = knex('users');
// conditional where case
if( typeof id === 'number' ) {
query.where('id', id);
}
// execute query
var result = await query;
```
很神奇吧。
在大多數的情況下,如果需要對調兩個變數的值,就需要使用到第三個變數來記錄暫存的值 : ```js temp = x; x = y; y = temp; ``` 然而,其實是可以使用 XOR 來耍點技巧來達成,不需要使用到 temp 變數 : ```js x = x xor y; y = x xor y; x = x xor y; ``` 不相信 ? 以下會進行一個實際的例子。 ```js // init values var x = 34; // 34 var y = 78; // 78 // do the swap x = x xor y; // 108 y = x xor y; // 34 x = x xor y; // 78 // final values y; // 34 x; // 78 ``` ### 什麼是 XOR XOR 是對 bit 的運算,以下例子可以說明 XOR 的動作 : |x|y|x xor y| |---| |0|0|0| |0|1|1| |1|0|1| |1|1|0| ### 動作分析 我們把上面的操作,使用不同的變數儲存起來,就會比較容易看得出分別。 ```js x1 = x xor y y1 = x1 xor y x2 = x1 xor y1 ``` 使用上面的數字 ( x = 34, y = 78 ),我們使用二進為計算一下。 #### 第一步 ```js x1 = x xor y ``` 我們使用二進位來計算說明 : ||||||||| |---| |||1|0|0|0|1|0| |xor|1|0|0|1|1|1|0| ||1|1|0|1|1|0|0| 而二進位 1101100 就是十進位的 108。 #### 第二步 ```js y1 = x1 xor y ``` 我們使用二進位來計算說明 : ||||||||| |---| ||1|1|0|1|1|0|0| |xor|1|0|0|1|1|1|0| ||0|1|0|0|0|1|0| 而二進位 100010 就是十進位的 34,已經把 x 的值移到 y 去了。 #### 最後一步 ```js x2 = x1 xor y1 ``` 我們使用二進位來計算說明 : ||||||||| |---| ||1|1|0|1|1|0|0| |xor||1|0|0|0|1|0| ||1|0|0|1|1|1|0| 而二進位 1001110 就是十進位的 78,已經把 y 的值移到 x 去了。 這樣就已經完成把變數 x 和 y 互換了 ! ### 使用純數的角度去證明 以下是證明 x2 = y 的算式 : ```js x2 = x1 xor y1; x2 = x1 xor (x1 xor y); // replace y1 x2 = (x1 xor x1) xor y; // associative law x2 = 0 xor y; // a xor a => 0 x2 = y; // 0 xor a => a; x2 now has y's original value ``` 以下是證明 y1 = x 的算式 : ```js y1 = x1 xor y y1 = (x xor y) xor y y1 = x xor (y xor y) y1 = x xor 0 y1 = x ``` 很有趣吧 !
在 ExtJS 內,我們會用 Proxy 來載入 Store 內容。以下是 Proxy 會自動加入的參數 :
- start - (number) 像 SQL 的 offset
- limit - (number) 像 SQL 的 limit
- page - (number) 頁數
- sort - (string) JSON format 的字串,用來排列結果
但是有時我們可能需要加入更多的參數才可以抽出資料,那應該要怎樣做呢?
### 動態方法
可以通過使用 `Proxy.setExtraParams()` 來動態設定額外的參數。
```js
grid.getStore().getProxy().setExtraParams({
foo: 'bar'
});
grid.getStore().load();
```
### 靜態方法
可以通過加入 `extraParams` 到 Proxy 來設定額外的參數。
```js
new Ext.data.Store({
autoLoad: true,
proxy: {
url: 'users.php',
type: 'ajax',
extraParams: {
foo: 'bar'
}
}
});
```
使用 ExtJS 就一定會自定義一些 Component 來使用,而自定義的 Component 如果要把 UI 的互動動作傳出去的話,就需要自定義 Event 了。
### 自定義 Component
看看下面例子 :
```js
Ext.define('MyApp.CustomForm', {
extend: 'Ext.form.Panel',
layout: {
type: 'vbox'
},
items:[{
xtype: 'textfield',
name: 'name'
}, {
xtype: 'button',
text: 'submit'
}]
});
```
上面定義了一個 `MyApp.CustomForm` 元件,入面定義了一個 submit button。如果要把 submit button 的 click 事件傳出去給 `MyApp.CustomForm` 接收,應該要怎樣做呢?
### 使用 `.fireEvent()` 方法
我們可以通過使用 `.fireEvent()` 方法來對元作發動事件 :
```js
{
xtype: 'button',
text: 'submit',
listeners: {
click: function(ele) {
ele.up('form').fireEvent('submitclick', 'message');
}
}
}
```
上面的代碼是使用 `MyApp.CustomForm`發動 `submitclick` 事件,並傳入 'message' 字串作為參數。
在生成 `MyApp.CustomForm` 元件時,我們可以設定 `submitclick` 事件的 listener。
```js
var form = Ext.create('MyApp.CustomForm');
form.on('submitclick', function(message) {
console.log(message);
});
```
這樣當 `MyApp.CustomForm` 內的 `button` 按下時,`MyApp.CustomForm` 就會發動 `submitclick` 事件所連結的 listeners。
JSON 的 Structure 可以像一個樹一樣去表達資料,但是傳統的 Relational Database 只可以使用一張張的 Table 來儲存資料,如果要把數張 Table 的內容合併為一個 JSON 來使用的話,可以通過使用 mapping 的方法把關聯的 ID 值串合起來。
### 例子
考慮到有以下的資料:
Table: users
|id|name|age|
|---|
|1|peter|11|
|2|tom|12|
|3|mary|13|
Table: user_fruits
|id|user_id|fruit|
|---|
|1|1|apple|
|2|1|orange|
|3|2|apple|
|3|2|banana|
|3|3|orange|
|3|3|banana|
|3|3|apple|
考慮到要抽出以下的 JSON 格式 :
```json
[
{"name":"peter", "age": 11, fruits:["apple","orange"]},
{"name":"tom", "age": 12, fruits:["apple","banana"]},
{"name":"mary", "age": 13, fruits:["orange","banana","apple"]}
]
```
應該要如何轉換呢?
### 解決方法
使用 Loop 是必需要的,因為而一個個 Data Object 互相 Mapping。不過在語法上可以使用 JS 一句內完成的。
```js
users.forEach(user => user.fruits = userFruits.filter(userFruit => user.id === userFruit.user_id).map(userFruit => userFruit.fruit));
```
以下是一個完整的示範 :
```js
// get users
var users = await db.query(`select * from users`);
// fetch user id as array
var userIds = users.map(user => user.id);
// check users is empty
if( users.length > 0 ) {
// fetch user fruits by user id
var userFruits = await db.query(`select * from user_fruits where user_id in (?)`, [userIds]);
// map user fruit data to users
users.forEach(user => user.fruits = userFruits.filter(userFruit => user.id === userFruit.user_id).map(userFruit => userFruit.fruit));
}
```
ExtJS 的 Combobox 方便好用,配上 Proxy 後可以自動載入 Ajax 的 JSON 內容。不過 API 內就好像沒有一個方法設定自動選擇第一選項。那應該要如何達成呢?
### 使用 Store 的 load 事件
我們可以通過使用 Store 的 load 事件,把 load 入 Store 的記錄抽取出來,然後使用 Combobox 的 `select(r)` 來設定選擇項目。
```js
/**
* on load event listener callback
*/
function load(this, records, successful, operation, eOpts) {}
```
可以透過 `load` 事件來取得 `records` 資料。
不過我們無法從 Store 取得 Combobox 元件,因為一個 Store 可以 Attach 到很多不同的元件,所以我們需要在第三方的元件上設定好關聯事件。這最好是 Combobox 的 top parent 元件。
### 設定方法
以下時通過使用 `load` 事件來自動選擇第一選項目的例子 :
```js
combobox.getStore().on('load', function(ele, records) {
if( records.length > 0 ) {
combobox.select(records[0]);
}
}};
```
而這段代碼運行的最佳時機,可以是 combobox 元件的 top parent 元件的 `initComponent()` 方法內。

要達到可以在 Grid 內即時修改 Cell 的內容,我們要為 Grid 加入 Plugin 設定才可以。
### 為 Grid Panel 加入 Plugin 設定
我們需要為 Grid Panel 加入以下的設定 :
```js
{
selModel: 'cellmodel',
plugins: {
ptype: 'cellediting',
clicksToEdit: 1
}
}
```
### 參數解說
- selModel : 選擇 Grid 內容時所以使用的 Model
- ptype : Plugin 的種類
- clicksToEdit : 按多少下滑鼠可以進入修改模式
### Column 設定
除了要設定好 Grid 外,還需要為你目標相修改的 column 進行設定 :
```js
{
columns: [
{header: 'name', dataIndex: 'name', editor: 'textfield'},
{header: 'name', dataIndex: 'name',
editor: {
completeOnEnter: false,
field: {
xtype: 'textfield',
allowBlank: false
}
},
]
}
```
### 參數解說
- editor (String) - 可以直接使用 xtype 來指定修改器的種類
- editor (Object) - 可以通過使用 field 方法來指定修改器的種類,更適合像 Combobox 一類的 Component。
### 例子
以下例子是從 ExtJS 的官方說明文件中節錄出來 :
https://docs.sencha.com/extjs/6.0.2/classic/Ext.grid.plugin.CellEditing.html
```js
Ext.create('Ext.data.Store', {
storeId: 'simpsonsStore',
fields:[ 'name', 'email', 'phone'],
data: [
{ name: 'Lisa', email: 'lisa@simpsons.com', phone: '555-111-1224' },
{ name: 'Bart', email: 'bart@simpsons.com', phone: '555-222-1234' },
{ name: 'Homer', email: 'homer@simpsons.com', phone: '555-222-1244' },
{ name: 'Marge', email: 'marge@simpsons.com', phone: '555-222-1254' }
]
});
Ext.create('Ext.grid.Panel', {
title: 'Simpsons',
store: Ext.data.StoreManager.lookup('simpsonsStore'),
columns: [
{header: 'Name', dataIndex: 'name', editor: 'textfield'},
{header: 'Email', dataIndex: 'email', flex:1,
editor: {
completeOnEnter: false,
// If the editor config contains a field property, then
// the editor config is used to create the Ext.grid.CellEditor
// and the field property is used to create the editing input field.
field: {
xtype: 'textfield',
allowBlank: false
}
}
},
{header: 'Phone', dataIndex: 'phone'}
],
selModel: 'cellmodel',
plugins: {
ptype: 'cellediting',
clicksToEdit: 1
},
height: 200,
width: 400,
renderTo: Ext.getBody()
});
```