2007年05月3日
關於 HTML label 元素無法作用
石頭成老大在我的「終於用 jQuery 做了一個有趣的服務」提到了在 IE 裡 label 元素的 for 屬性是有 bug 的,我研究了一下,把心得分享給大家。
原本我一直以為表單中的 input 元素只要設定 display: none 或是 visibility: hidden 的話,就沒辦法送出;不過 W3C 中在「 17.13.2 Successful controls 」一節中有明確的規範:
Hidden controls and controls that are not rendered because of style sheet settings may still be successful.
也就是說不論是 display:none 或是 visibility: hidden ,只要是利用 CSS 來讓 control 消失的,都還是應該把值送給後端。
我用了以下的程式測試了一下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>TEST</title>
<style type="text/css">
.hide {
visibility: hidden;
}
.none {
display: none;
}
</style>
</head>
<body>
<form action="" method="post">
<label for="type1">Type1</label>
<input type="radio" class="hide" id="type1"
name="type[]" value="1" checked="checked" />
<label for="type2">Type2</label>
<input type="radio" class="none" id="type2"
name="type[]" value="2" />
<input type="submit" value="送出" />
</form>
<a href="<?php echo $_SERVER['PHP_SELF']; ?>">重新整理</a>
<?php var_dump($_POST); ?>
<script type="text/javascript">
window.onload = function () {
var t2 = document.getElementById('type2');
t2.focus();
}
</script>
</body>
</html>
結果發現 Firefox 、 Opera 不論有沒有預設的 checked ,在點選 label 後都會正確送出對應的 radio 欄位值。
而 IE6 、 IE7 事實上也是支援,只是它們僅能送出有預設 checked 的 radio 欄位;如果完全沒有預設 checked 的 radion 欄位時,因為點選 label 仍無法讓 radio 的值改變,因此也無法送出任何 radio 值 (就如石頭成老大所說明的) 。
所以我原先誤認 IE 不會理會 display: none 或是 visibility: hidden 的元素,其實是 label 無法作用;但更正確的來說,則是因為 IE 無法接受把 focus 放在不可見的元素上。
換句話說,我們沒辦法在 IE 上 focus 任何被 display: none 或是 visibility: hidden 的元素;而 label 則是 HTML 用來 focus 對應 input 元素的捷徑,也因此就不會有作用了。
註:所以我在「終於用 jQuery 做了一個有趣的服務」裡的作法就是為了避開這個問題。
最後這是不是 bug 呢?我個人倒覺得像是 IE 開發團隊自己的想法。不過 IE 無視於 W3C 標準也不是第一次了,所以石頭成老大說這是 IE 在 W3C 實作上的 bug 其實也不為過。至於解法,大家可以直接參考石頭成老大的說明,這裡我就不多寫了。
2007年04月19日
IE7 對錯誤 Hostname 在處理 Cookie 上的問題
昨天凱發現了一個 IE7 的問題,我們兩個做了一個討論後在這裡把結果記錄下來。
要重現這個問題很簡單,請照著以下的步驟試試看。
首先在 Windows 的 hosts 檔裡加入粗體部份:
127.0.0.1 localhost
127.0.0.1 host_name
然後在網站根目錄底下建立一個 session.php ,內容如下:
<?php
session_start();
$test = (isset($_SESSION['test']))
? (int) $_SESSION['test']
: 0;
echo $test;
$test ++;
$_SESSION['test'] = $test;
然後試試用 IE7 瀏覽以下網址:
http://host_name/session.php
試試看去重新整理這個頁面,會發現 Session 沒辦法動作。
不過同樣的網址在 IE6 和 Firefox 上, Session 都會在每次重新整理時正常加一;但如果把 host_name 換成 localhsot ,則每個瀏覽器都會正常動作。
一開始我以為是 IE7 有支援 HttpOnly Cookie 的設定,不過這項設定是在 PHP 5.2 以後才會有,而凱用的是 PHP 5.1.6 ;然而 Firefox 也支援 HttpOnly Cookie 卻能正確運作,所以並不是這個問題。
凱告訴我,他在 hosts 上加入 127.0.0.1 my.xxx.net 的設定,然後再用以上三種瀏覽器去瀏覽 http://my.xxx.net/session.php ,發現 Session 是可以運作的。這時我們就想到會不會是 host_name 有底線的關係,所以凱很快地重新把底線去掉後再用 IE7 測試,結果就正常了。
最後我們兩個人的結論是:
在 IE7 中,有底線的 hostname 會沒辦法正確使用 Cookie 。
其實微軟不建議 Hostname (或 Domain Name ) 使用底線字元,這個可以參閱「為電腦、 網域、 站台及 OU 命名慣例在 Active Directory 」一文。
2007年03月16日
透視 WebMVC
2007年02月4日
5 個我在舊專案的 XHTML 中常發現的問題
最近在一些維護一些舊專案裡的頁面,它們都已經採用了 XHTML 1.0 Transitional 。一個正確的 XHTML 1.0 Transitional 頁面的範例如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=big5" />
<title>Page Title</title>
</head>
<body>
...
</body>
</html>
當然大部份頁面是符合的,不過我還是發現了一些問題;在這裡我把它們記下來,供往後開發時參考。
...繼續閱讀2007年01月3日
將資料和程式分離
有位 jocosn 網友問了一個我覺得很棒的問題,我把我的觀點提出來供大家參考。
註:以下我會以引用區塊的方式來標示該網友的問題,然後以回答該問題的語氣說明。且基於本人龜毛的個性,也會把內容稍作排版。
如果我有一個 welcome.php,它的樣版檔是 welcome.tpl.htm,但是兩個檔案的編碼不相同,也就是 php 使用 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> , tpl.htm 使用 <meta http-equiv="Content-Type" content="text/html; charset=big5">,結果螢幕顯示時會以 utf-8 編碼格式顯示 tpl.htm 檔案,必須再手動挑選 big5,請問這種情況有辦法解決嗎?還是程式這種架構有問題?
使用 big5 是因為 tpl.htm 檔案有用到 mailto,像這樣:
<a href='mailto:h...@yahoo.com.tw?subject=其他問題&body=問題描述:%0A%0A%0A%0A聯絡方式%0A地址:%0A電話:%0A聯絡人:%0A'>寫信連絡</a>如果改用 utf8,點下 mailto 開啟 outlook 6 會出現亂碼。所以採用 big5 才會正常顯示中文字。但是 php 檔案因有些部份未使用樣板處理一些簡單的小訊息 (如顯示"檔案建立成功") ,所以採用 utf8,以免顯示中文像「功」字會出現問題。
像這種情況下,請問該怎麼處理,或是有更好的解決方式嗎?
事實上你的 template 還是可以用 utf-8 的,只不過有些地方要做一些手腳。
在這之前,先給你一個觀念:把資料和程式分離。怎麼說呢?直接給你看例子好了:
首先我把 mailto 會用到的中文字另存成一個 ini 檔:
[mailto]
subject = "其他問題"
body = "問題描述:\n\n\n\n聯絡方式\n地址:\n電話:\n聯絡人:\n"
特別要注意的是,這個 ini 檔是用 big5 來存檔的。
然後我在 index.php 裡把它載入:
<?php
$mailto = parse_ini_file('mailto.ini');
$subject = urlencode($mailto['subject']);
$body = str_replace('%250A', '%0A', urlencode(str_replace('\n', '%0A', $mailto['body'])));
include('template.php');
這邊要特別注意的是 php 檔是用 utf-8 來存檔 (事實上你也可以用 ANSI 來存) 。
最後來看看 template.php :
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>測試 mailto</title>
</head>
<body>
<a href="mailto:jxxxxxxxxx@gmail.com?subject=<?php echo $subject; ?>&body=<?php echo $body; ?>">寫信連絡</a>
</body>
</html>
這裡一樣也是用 utf-8 存檔。
執行它看看,跑出來的結果是不是符合你的需要呢?
這裡就導出一個觀念了: PHP 在處理資料時,其程式檔案的編碼是不會影響資料本身的編碼的。
在上面的例子裡, parse_ini_file 取得的資料就是 mailto.ini 本身的編碼 (也就是 big5) ,而 PHP 也不會因為自己的檔案編碼是 utf-8 而改變 parse_ini_file 所取得的資料編碼。因此這時候的「其他問題」和「問題描述...」都能夠用在 template 上...
不過等一下,如果你直接把 $mailto['subject'] 和 $mailto['body'] 套入 template.php 的話,那就慘了。為什麼呢?因為 big5 編碼的內容在 utf-8 編碼的網頁裡,瀏覽器不知道該怎麼解釋它們 (也就是一堆亂碼啦) 。所以這裡我們要利用 urlencode 這個函式,來將 big5 的文字做 URL 編碼。
至於 \n 的部份只是個表示方式,實際上我們還是要手動將它換成 %0A (雖然我換的方式還滿醜的) 。
當然不只是文字檔案,就連資料庫中取出的資料也是相同的道理。這點你可以參考我先前寫的 MySQL 中文編碼徹底研究,裡面有詳細的內容。
再請問,如果用 smarty 時,php 檔案是不是應該儘量只有 php 程式碼?
依照上面的觀念,你認為我這裡會給什麼樣的答案呢?想想看吧 :)
另外 php 檔案存成 utf-8 或 big5 有差嗎?還是說網頁儘量不要存成 big5,以免特殊中文字會出現錯誤?
例如 php 檔案中像這樣 $mysmarty->assign('myVar1','許蓋功') ,存成 big5,我在測試時,就會出現錯誤 Parse error: parse error, unexpected T_STRING
PHP 引擎會對 <?php ?> 裡面的特殊字元很敏感,所以上面的觀念一樣適用在這裡;當然如果是在 <?php ?> 外的話 (通常是 HTML ) 就沒問題了。
希望上面的說明能讓你清楚相關的觀念 :)
2006年05月8日
[心得] 網頁程式開發建議
這篇是寫給新同事以及我自己備忘用的,也歡迎大家提出指正。
在撰寫網頁程式時,有些基本的東西要非常注意,這樣寫出來的程式才不容易出現問題,也才能夠交給客戶。以下就是我自己的開發心得,供大家參考。
利用明確的指定方式來取得頁面傳來的變數
不管是 GET 或 POST 的參數,我會希望明確地指定它的型態,例如 id 應該是整數, content 是文字等。而且我也常常發現有人把接到這些變數後,沒有事先做處理就直接串到程式中,這是非常危險的一件事情!
最明顯的例子就是 SQL Injection 了,我想這應該不用多說了。總而言之,不要過份相信用戶端傳來的資訊,把它轉換成你能掌握的型態再用吧。
資料庫的連結每個頁面只作一次
資料庫連結是網站頁面常做的事情,但是儘可能不要讓一個頁面產生太多的資料庫連結。我們可以利用 Singleton 模式來取得資料庫連結物件,這樣可以確保程式裡只會用到一個資料庫連結;當然需要連結不同資料庫的話就另當別論。
所以我們要確定資料庫會在頁面初始化時連結,頁面結束時關閉。這樣有單一的入口與出口,程式也就不容易出現奇怪的問題。
相同的邏輯不要寫兩遍以上
如果發現有兩支程式會用到相同的程式邏輯時,不要猶豫,把它抽出來變成類別或函式 (最好是類別,原因在下一則) 。因為如果哪一天需要更新程式的邏輯時,你只需要更改一支程式即可。對健忘的人來說,這點尤其重要。
我就遇過有人把產生選單的邏輯重複寫在十多支程式裡,結果有次客戶要求要修正其中一個地方,可憐的維護人員 (就是我啦) 就得一支一支地去翻出來改。
利用物件來管理錯誤
我這裡指的錯誤是任何預期中的狀況,也就是你不希望使用者操作的方式,例如編號不存在或是檔案大小超過限制等。
當錯誤發生時,不要立刻結束,應該利用錯誤上升機制來讓通知上一層的程式,最後再由頁面控制程式來決定要如何處理錯誤。我常常會遇到有人在函式裡利用 exit 離開程式,但是這時候頁面的資料庫或其他物件等等都還沒釋放掉;雖然程式平台可能會幫你做,但那總是很難預期。
所以我建議不要使用函式,而改為使用物件的方法,然後利用類似 PEAR::isError() 來判斷是否執行成功;如果失敗的話就把錯誤往上丟,直到頁面能夠控制為止。
該釋放的要記得釋放
不管是物件還是資料庫,都應該在頁面結束前將它們銷毀或關閉,而不要過於依賴程式執行平台。網頁程式是很多人會同時存取的,如果沒有正確地將資源即時釋放掉的話,久而久之就會造成系統效能上的不穩定。
而釋放的動作要什麼時候做呢?記住一句話:誰開的就誰負責關。例如上面頁面控制程式開啟資料庫連結,那就在頁面控制程式的最後把資料庫連結關閉。類別建構函式產生的物件,就在類別解構程式裡銷毀。函式開的頭,當然就在函式尾收掉;不過有個例外,那就是這個函式如果本身就是要回傳產生的物件時,那就不能把它給釋放囉,而是要改為呼叫這個函式的程式來釋放。
利用樣版技術
樣版是用來分離程式邏輯與視覺頁面的,也常常有人用 MVC 這個模式來稱呼它。然而兩者分離除了不相互干擾外,其實還有一個好處:那就是程式可以在錯誤發生後,決定要顯示的結果。就像上面提到的錯誤管理,當我們在頁面控制程式取得錯誤訊息時,我們就可以而用置換樣版來避免掉頁面的錯誤,或者是導向別的處理程式。
這點我覺得 PHP 的 Smarty 就考慮得很好,因為它是後期頁面綁定 (Binding) ,而不會像傳統樣版引擎在前期就把頁面拉進程式處理,導致錯誤發生時徒然浪費處理時間 (當然要看怎麼設計的) 。
徹底瞭解開發環境的性質
網頁程式和一般應用程式 (例如視窗應用程式) 在本質上是有差異的,這些差異不僅是在操作上,就連執行的過程都非常的不一樣。雖然現在有 AJAX 或其他技術可以縮小彼此的差距,但是它還是建構在 HTTP 這個無狀態協定之上。身為網站程式開發人員,其實應該要瞭解這些基礎,而不要只熟稔某些已經被包裝過的技術就顯得自得意滿。
最簡單的就是伺服端程式與用戶端程式之間的溝通,例如 PHP 和 JavaScript 。我常常在網路上看見有人問道:要如何讓 PHP 和 JavaScript 之間的變數互通?如果瞭解 HTTP 執行的過程,那麼你就會自己發現這些問題的答案。
