<![CDATA[Creativecrap]]> https://creativecrap.com my works, my thoughts, my photographs... https://creativecrap.com/images/favicon-144.png Creativecrap https://creativecrap.com zh-tw Mon, 20 Feb 2017 07:30:17 +0800 git-gui under macOS sierra crash fixes https://creativecrap.com/blog/misc/git-gui-under-macos-sierra-crash-fixes.html https://creativecrap.com/blog/misc/git-gui-under-macos-sierra-crash-fixes.html Mon, 07 Nov 2016 00:00:00 +0800 似乎是 sierra 帶的 Tk 8.5 有相容的問題,將 Wish 換回前一個版本看來正常多了 cd /usr/local/git/share/git-gui/lib/Git Gui.app 似乎是 sierra 帶的 Tk 8.5 有相容的問題,將 Wish 換回前一個版本看來正常多了

  
cd /usr/local/git/share/git-gui/lib/Git Gui.app/Contents/MacOS  
mv Wish Wish.bak  
cp /System/Library/Frameworks/Tk.framework/Versions/8.4/Resources/Wish.app/Contents/MacOS/Wish ./  
  

如果是透過 brew 裝的 git 則應該是在 /usr/local/Cellar/git

Update: 11/11/2016

Wish 8.4 雖然沒有問題了,但是由於是使用較舊的 Carbon framework (ATUSI engine) 而不是 Cocoa (CoreText) 所以字實在太醜了。

改安裝 ActiveTcl 8.6,安裝的位置在 /Library/Frameworks/Tk.framework/Versions/8.6/Resources/Wish.app/Contents/MacOS/Wish,同樣再複製 Wish 給 git-gui 使用。

]]>
ethan@creativecrap.com
evernote https://creativecrap.com/blog/murmur/evernote.html https://creativecrap.com/blog/murmur/evernote.html Thu, 22 Sep 2016 00:00:00 +0800 After messed up Skitch, and now 2 devices limits, say good-bye to Evernote and Hi Note with iCloud. After messed up Skitch, and now 2 devices limits, say good-bye to Evernote and Hi Note with iCloud.

]]>
ethan@creativecrap.com
Firefox48 will not allow unsigned extensions to be installed https://creativecrap.com/blog/misc/firefox48-will-not-allow-unsigned-extensions-to-be-installed.html https://creativecrap.com/blog/misc/firefox48-will-not-allow-unsigned-extensions-to-be-installed.html Wed, 03 Aug 2016 00:00:00 +0800 Firefox 強迫要求所有的 Add-on 都需要經 AMO 簽囑的時間還是到了,所以 xpinstall.signatures.required 此項設定自 v48 起正式被淘汱,如果需要安裝未簽 Firefox 強迫要求所有的 Add-on 都需要經 AMO 簽囑的時間還是到了,所以 xpinstall.signatures.required 此項設定自 v48 起正式被淘汱,如果需要安裝未簽囑的 Add-on 則還是可使用 Dev Edition, Nightly, ESR, unbranded 版本。

安全性的問題,瀏灠器限制 extension 的條件也越來越嚴刻,看來需要自己寫 WebExtensions 的狀況也不遠了。

]]>
ethan@creativecrap.com
Upcoming OkidoKey Features https://creativecrap.com/blog/misc/upcoming-okidokey-features.html https://creativecrap.com/blog/misc/upcoming-okidokey-features.html Wed, 15 Jun 2016 00:00:00 +0800 And this probably the last version with Swift 2, will moving to Swift 3. Added: Bluetooth keyboard And this probably the last version with Swift 2, will moving to Swift 3.

  • Added: Bluetooth keyboard full width character fixes. i.e. Logitech Bluetooth Keyboard products
  • Added: Traditional / Simplified Chinese live conversion
  • Added: Clipboard - For bluetooth keyboard testing, use as copy&paste board when using bluetooth keyboard under multitasking
  • Added: Input Method Locale property (TC/SC conversion)
  • Changed: Show input method state in Note editor
  • Fixed: Candidate cell width issue under iOS9 Split View Multitasking
  • Fixed: What's New close issue
]]>
ethan@creativecrap.com
Incompatible CPU detected https://creativecrap.com/blog/murmur/incompatible-cpu-detected.html https://creativecrap.com/blog/murmur/incompatible-cpu-detected.html Fri, 06 May 2016 00:00:00 +0800 收到 Docker for Mac beta,但是還在 Core 2 Duo 的 Mac mini 被嫌棄了... docker-mac-beta.jpg

收到 Docker for Mac beta,但是還在 Core 2 Duo 的 Mac mini 被嫌棄了...

]]>
ethan@creativecrap.com
Please don't use until https://creativecrap.com/blog/murmur/please-dont-use-until.html https://creativecrap.com/blog/murmur/please-dont-use-until.html Tue, 12 Apr 2016 00:00:00 +0800 Please don't use #id for styling until you know what identity means. Please don't use lots of jQuery Please don't use #id for styling until you know what identity means.
Please don't use lots of jQuery plugins until you understand jQuery and javascript.
Please don't use lots javascript animations until something can't be done with css animatiom.
Please don't use less or sass until you uderstand css.
Please don't use reat until you uderstand html and css.

]]>
ethan@creativecrap.com
Apps https://creativecrap.com/app/apps.html https://creativecrap.com/app/apps.html Sat, 26 Mar 2016 00:00:00 +0800 Most of these are my personal published side projects. I use them a lot and hope it helps someone w Most of these are my personal published side projects.

I use them a lot and hope it helps someone who needs it too.

OkidoKey

iOS 8.0+

第三方中文輸入法鍵盤,可於內建記事本中使用藍芽鍵盤。

OkidoKey Charsets

Open source data of OkidoKey

OkidoKey 鍵盤配置、輸入法字根等資料開源專案

iLyricsCloud

Self-hosting Web Service

LyricsCloud is a self-hosting web service which can management, search artworks and lyrics.

It's the middle man of iLyrics widgets.

ibus2cin

Command line tools for Mac, Linux and Windows

嘸蝦米輸入法字根表匯出工具程式



Under-development

They may still works but out-dated or drop support by its platform, consider using above apps instead if any.


zhim! extended

iOS 5.0~7.x

自訂中文輸入鍵盤記事本軟體,支援藍芽鍵盤。

For newer devices, please use OkidoKey instead.

zhim! converter

Web serivce

提供 zhim! extended 所需要的輸入法字典檔線上轉檔服務。

iLyricsWidget

Dashboard widget for Mac OS X 10.5+

Auto discover the artworks, lyrics of current playing song from iTunes.

Requires iLyricsCloud as backend.

zhim!

iOS 3.0~4.x

自訂中文輸入鍵盤記事本軟體,支援藍芽鍵盤。

For newer devices, please use OkidoKey instead.

iLyrics Music

iOS

Just like iLyrics Widget but works on iOS devices.

iLyrics Mobile

iOS

Just like iLyrics Widget but works on iOS devices.

]]>
ethan@creativecrap.com
Angkor Wat https://creativecrap.com/gallery/journey/angkor-wat.html https://creativecrap.com/gallery/journey/angkor-wat.html Sat, 26 Mar 2016 00:00:00 +0800 Interesting stories of the missing city, beautiful structures and carves. Some photos I took in Angkor Wat in 2004.
Interesting stories of the missing city, beautiful structures and carves.

]]>
ethan@creativecrap.com
OkidoKey Lite 1.2.1 https://creativecrap.com/blog/murmur/okidokey-lite-121.html https://creativecrap.com/blog/murmur/okidokey-lite-121.html Fri, 04 Mar 2016 00:00:00 +0800 經歷了數次的退件,跟 OkidoKey 差距了好幾個版本之後,Lite 版本終於通過審核了。 經歷了數次的退件,跟 OkidoKey 差距了好幾個版本之後,Lite 版本終於通過審核了。

]]>
ethan@creativecrap.com
Let's Encrypt https://creativecrap.com/blog/misc/lets-encrypt.html https://creativecrap.com/blog/misc/lets-encrypt.html Thu, 18 Feb 2016 00:00:00 +0800 把 SSL 轉為 Let's Encrypt 了,順道也調整了一些 DHParams, ciphers, STS 設定 測試有獲得 A+ SSL_Server_Test.png

把 SSL 轉為 Let's Encrypt 了,順道也調整了一些 DHParams, ciphers, STS 設定
測試有獲得 A+

]]>
ethan@creativecrap.com
Qihoo360 https://creativecrap.com/blog/misc/qihoo360.html https://creativecrap.com/blog/misc/qihoo360.html Tue, 16 Feb 2016 00:00:00 +0800 繼 Opera 接下來連 StartSSL 也遭毒手了,看來得找時間來轉到 Let’s Encrypt 了 https://letsencrypt.org https://pierrekim.git 繼 Opera 接下來連 StartSSL 也遭毒手了,看來得找時間來轉到 Let’s Encrypt 了

https://letsencrypt.org
https://pierrekim.github.io/blog/2016-02-16-why-i-stopped-using-startssl-because-of-qihoo-360.html
http://www.opera.com/blogs/desktop/2016/02/stillyouropera-for-computers

]]>
ethan@creativecrap.com
Ad Networks https://creativecrap.com/blog/misc/ad-networks.html https://creativecrap.com/blog/misc/ad-networks.html Sun, 24 Jan 2016 00:00:00 +0800 iAd is shutting down and I can't get AdMob to work under iOS9, huh! Any Ad networks suggestion? iAd is shutting down and I can't get AdMob to work under iOS9, huh!
Any Ad networks suggestion?

]]>
ethan@creativecrap.com
OkidoKey - Privacy Policy https://creativecrap.com/app/okidokey-privacy-policy.html https://creativecrap.com/app/okidokey-privacy-policy.html Fri, 08 Jan 2016 00:00:00 +0800 此章節包含 OkidoKey 主程式及其擴充鍵盤功能的隱私權聲明。 隱私權聲明 維持您的信任及保護您的個人資料安全,是 OkidoKey 核心任務。 您可能在安裝 O 此章節包含 OkidoKey 主程式及其擴充鍵盤功能的隱私權聲明。

隱私權聲明

維持您的信任及保護您的個人資料安全,是 OkidoKey 核心任務。

您可能在安裝 OkidoKey 過程中,會看到第三方鍵盤外掛可能會收集敏感的資料,例如密碼、信用卡或其他個人資訊的警示訊息。

OkidoKey 在設計及規劃上,即避免蒐集、儲存或傳送任何您所輸入的資料。

允許完整取用

根據第三方鍵盤開發條款及使用限制,OkidoKey 因需要下列使用限制,而要求使用者開啟 "允許完整取用" 設定。

  • 鍵盤及其主程式,需要共用檔案或資料。

更多取用的限制條款資訊,請參閱第三方鍵盤開發條款 (表 11-1) https://developer.apple.com/library/ios/documentation/General/Conceptual/ExtensibilityPG/Keyboard.html


FAQ

支援哪些 Apple 裝置?

OkidoKey 需要 iOS 8 及以上版本,建議保持在最新版本,以獲得更好的體驗。

如何新增輸入法?

輸入法需要對應的 .cin 表格檔。

如果你需要的表格檔已存在網路上,可以直接於 OkidoKey 表格檔列表畫面中,點選右上方 + 開啟下載對話框,再填入網址下載。

或是將已有的表格檔,郵寄到自己的信箱,再指定以 OkidoKey 開啟附件即可匯入。

點擊任何已匯入的表格檔,即可安裝該輸入法。

什麼是表格檔?

表格檔是文字及其輸入法的拆字字根組合,再包含一些輸入法相關的資訊的純文字檔,並以 UTF-8 編碼格式存檔。

想要知道更多有關表格檔的內容或製作方式,請參考OpenVanilla 刪除、修改與設計自己的輸入法說明文件。 https://github.com/lukhnos/openvanilla/blob/master/CinHowTo.markdown

如何取得其他輸入法表格檔?

常見的輸入法表可,可由 OpenVanilla 原始碼網站下載,例如: 倉碩、大易、注音、行列等。

OpenVanilla 原始碼網站: https://github.com/lukhnos/openvanilla/tree/master/DataTables

如何取得嘸蝦米輸入法表格檔?

嘸蝦米為行易有限公司所發行的軟體,若所購買的嘸蝦米無提供 .cin 表格檔,仍可由已安裝了嘸蝦米輸入法的電腦中提取。

Windows 及 Mac 提取方法,請參考嘸蝦米指南。 http://creativecrap.com/app/zhim-boshiamy-walkthrough

若您為行易官方網站會員,也可使用官方提供給 IBus 輸入法所使用的表格檔匯出,請參考 ibus2cin 專案。 http://creativecrap.com/app/export-cin-table-from-ibus-boshiamy-using-ibus2cin

是否支援實體藍芽鍵盤?

iOS8 keyboard extension 目前無法支援實體鍵盤,因此並無法使用藍芽鍵盤。

然而 OkidoKey Notes 則為了實體鍵盤而設計,因此在 OkidoKey Notes 上可配合藍芽鍵盤使用。

這個訊息代表什麼? "尚未啟用任何輸入法"

這個訊息表示,尚未在 OkidoKey 中安裝輸入法,或所啟用的輸入法表格檔遺失或損毀。

請先於 OkidoKey 設定中,確認已有安裝輸入法,並於系統設定 > 一般 > 鍵盤 > OkidoKey 鍵盤項目中,確認已開啟 "允許完整取用"。

如上述項目皆無誤,但仍有些訊息,請嘗試先安不同的輸入法,例如注音,再安裝欲使用的輸入法。

仍有問題,請嘗試重新安裝 OkidoKey。

這個訊息代表什麼? "表格檔安裝失敗,請確認檔案格式是否正確"

這個訊息表示您所使用的表格檔格式不正確,請確認該檔案的格式及內容是否正確。

為什麼要啟用 "允許完整取用" 選項,這樣安全嗎?

OkidoKey 需要讀取輸入法相關的檔案及表格檔,因為此需要開啟 "允許完整取用" 設定。

更詳細的內容,請參閱隱私權聲明章節。

沒有適合我使用的輸入法的鍵盤該怎麼辨?

歡迎來信或 Twitter 訊息詢問,可以再進一步討論您所需要的鍵盤內容。

如果您的如何建立鍵盤配置有興趣,或願意分享您的鍵盤配置,歡迎到 OkidoKeyCharsets 專案留言分享。 https://github.com/ethanliu/OkidoKeyCharsets

如何與我們連絡?

如果您有其他使用上問題或建議請來信至 apps+okidokey@creativecrap.com 或 Twitter @ethanliu

]]>
ethan@creativecrap.com
OkidoKey - The Input Method Keyobard Kit https://creativecrap.com/app/okidokey.html https://creativecrap.com/app/okidokey.html Tue, 01 Dec 2015 00:00:00 +0800 A note app supports various kinds of Chinese input methods for both hardware keyboard and keyboard extension. 可配合藍芽鍵盤使用中文輸入法的筆記本及自訂鍵盤

OkidoKey

THE INPUT METHOD KEYBOARD KIT

Features

  • 含常用中文輸入法字根表清單,包含行列10、行列30、注音、倉頡、大易、輕鬆輸入法及拼音等等
  • 繪文字
  • 同音字查詢及字根、注音及拼音反查
  • 藍牙鍵盤、筆記及剪貼板
  • 中英文混合輸入
  • 自訂鍵盤可使用多組鍵盤配置
  • 行列30 輸入法優化,並支援一級簡碼、二級簡碼及特別碼
  • 括號自動配對及全形符號
  • 拼字檢查、連絡人名稱及使用者文字替換
  • 支援 .cin 輸入法字根表格
  • 支援輸入法候選字選擇鍵
  • 自訂鍵盤
  • 繁體中文字詞
  • 繁體/簡體中文輸出選擇
  • 自動複製文字的剪貼板
  • 自訂鍵盤使用二指遊標控制

Notes specially designed for

Hardware Keyboard

Input Methods

OkidoKey 中文輸入法可分為兩個主要部份 - 輸入法字根表及鍵盤配置。

行列30

OkidoKey 遵循行列輸入法規格書定義,支援一級、二級簡碼及特別碼。當於輸入法清單中下載行列30字根表時,會同時下載 array30-shortcode.cin 及 array30-special.cin 兩個字根表,在安裝行列字根表時,若上述任一個表格檔案同時存在,則在安裝時會一併安裝,如果不想要使用簡碼或特別碼,則在安裝行列表格檔之前,將不需要的表格檔刪除即可。

行列10

行列10為特別為行動裝置設計的行列輸入法,僅需 0~9 的數字鍵,這也是行列10首次發表於 iOS 平台。使用行列10輸入法時,建議搭配 Keypad 鍵盤使用。

特別感謝行列字根表的主要維護者老刀,經由他的協助 iOS 平台上也可完美使用行列輸入法。

嘸蝦米

OkidoKey 支援嘸蝦米輸入法,但需由使用者自行提供合法的字根表格檔。

更多資訊請參考嘸蝦米使用指南ibus2cin 專案。

嘸蝦米版權為行易有限公司所有,需購買嘸蝦米相關軟體請洽詢行易官方網站。

匯入 .cin 檔案

匯入 .cin 字根表格檔

Email

  1. 確認檔案副檔名為 .cin
  2. 將檔案透過 email 寄給自己
  3. 在裝置上開啟此信件
  4. 選擇表格檔附件,並選擇分享或更多按鍵,選擇分享於 OkidoKey
  5. 開啟 OkidoKey 輸入法設定選項,應可看到新增的表格檔。若檔案未出現,請重複上述步驟

以網址下載

  1. 確認檔案副檔名為 .cin
  2. 將檔案上傳至任何可產生直接下載連結的地方,例如 http://example.com/mycustom.cin
  3. 開啟 OkidoKey 輸入法設定選項
  4. 點擊右上方 + 鍵
  5. 選擇 "我有下載連結" 並輸入步驟 2 的連結並完成下載
  6. 字根表在下載完成後即會出現在輸入法列表中

鍵盤配置

鍵盤配置雖然不影響 OkidoKey 輸入法的使用,但搭配適當的鍵盤仍可獲得比較好的體驗。OkidoKey 預載了常見輸入法所使用的輸入法鍵盤,並以開放原始碼專案方式釋出,相關資訊請訪問 OkidoKeyCharsets 專案

鍵盤配置分為主要鍵盤及次要鍵盤。主要鍵盤會配合輸入法使用,而次要鍵盤則為一般鍵盤,會直接送出所按下的按鍵,適用於英文或符號鍵盤。

OkidoKey ♥️ Bluetooth Keyboard

當要大幅輸入文字時,實體鍵盤仍是最佳的輸入界面。然而第三方鍵盤並非為實體鍵盤設計,因此 OkidoKey 有兩個絕佳的解決方式。OkidoKey 筆記及剪貼板皆支援外部鍵盤,包含 Apple 的 Smart Keyboard。

筆記

簡易式的筆記系統,可以資料夾方式分類管理。支援 iCloud 可以在不同裝置間存取,及僅存在該裝置中的資料夾,不會與其他裝置同步也不需要存取網路。

剪貼板

剪貼板不同於筆記,在於剪貼板在關閉後不會儲存任何內容。搭配 iOS9 的分割畫面多工模式使用時,則可做為實體鍵盤的輸入界面。

開啟 OkidoKey 於主要或次要應用程式,打開剪貼板即可開始輸入,不需要來回瀏灠筆記資料夾。

剪貼板會自動複製所輸入的內容,只要直接選擇要貼上文字的地方,並執行貼上動作即可,不需要手動選取文字/複製等動作。

* zhim! extended 的使用者,可匯入儲存在 iCloud 中的文件至 OkidoKey 以便繼續使用。

More Features

同音字

將任一候選字往上推後放開,則會顯示其他同音字。

字根反查

將任一候選字往下拉後放開,則會顯示該候選字的所有拆碼、注音及拼音。

拼字檢查

依系統語系於輸入時做提供其他拼字選項,可使用語系包含英文、西文、法文、德文等 iOS 系統支援語系。

連絡人及文字替換

連絡人名稱及使用者定義之文字替換。

* Keyboard extension only

繪文字

可直接輸入繪文字而不需要切換至繪文字鍵盤。

繁體中文詞庫

中文輸入時顯示聯想字詞。

全形符號

使用符號鍵盤時,使用 Shift 鍵切換半形或全形符號。

自動配對括號

當輸入括號、引號等符號時,會自動插入結尾符號,而不需要另行輸入。

Compare

  OkidoKey OkidoKey Lite zhim! extended
iOS 8.0 以上版本 8.0 以上版本 7.0 及較早版本
價格 $210 免費
含有廣告
免費
含有廣告
內購 $90
移除廣告
藍牙鍵盤
自訂鍵盤
同音字
字根反查
文件 裝置
iCloud
裝置
iCloud
裝置
Evernote
Dropbox
iCloud
字根表格 CIN 表格檔
App 中匯入
CIN 表格檔
App 中匯入
CIN 表格檔
App 中匯入
線上轉檔服務
App Store Buy Download Download

OkidoKey Lite

The free version of OkidoKey Lite contains third party advertisements and all of the features.

We make OkidoKey with love, and would like all of you enjoy the benefits of being with OkidoKey for free!

]]>
ethan@creativecrap.com
Forgot the npt service https://creativecrap.com/blog/murmur/forgot-the-npt-service.html https://creativecrap.com/blog/murmur/forgot-the-npt-service.html Fri, 13 Nov 2015 00:00:00 +0800 Always know there is something worng about the time but never pay attention to check util some stran Always know there is something worng about the time but never pay attention to check util some strange behavior caught me then realize that I forgot to install ntp and setup the correct timzone on aws server.

sudo apt-get install ntp
sudo ln -sf /usr/share/zoneinfo/Asia/Taipei /etc/localtime
sudo reboot

]]>
ethan@creativecrap.com
Disable rootless on Mac OSX 10.11 https://creativecrap.com/article/misc/dosable-rootless-on-mac-ox-10.11.html https://creativecrap.com/article/misc/dosable-rootless-on-mac-ox-10.11.html Thu, 01 Oct 2015 08:00:00 +0800 OS X 10.11 及 iOS9 引進了 System Integrity Protection 也就是俗稱的 rootless 的保護機制,簡單的說他限制了所有非 Apple 軟體去更動系統自帶的檔案,這包含了系統上最高權限的帳號 root 也無法更動,所以即使有 root 權限的 sudo 命令,也會只得到 "Operation not permitted" 或 "can’t be modified or deleted because it’s required by OS X" 的結果。

OS X 10.11 及 iOS9 引進了 System Integrity Protection 也就是俗稱的 rootless 的保護機制,簡單的說他限制了所有非 Apple 軟體去更動系統自帶的檔案,這包含了系統上最高權限的帳號 root 也無法更動,所以即使有 root 權限的 sudo 命令,也會只得到 "Operation not permitted" 或 "can’t be modified or deleted because it’s required by OS X" 的結果。

立意是好的這毋庸置疑,對一般的使用者來說只有更安全,但對於進階者說來不便應該會多於安全。例如以字型來說,在安裝完或系統版本更新之後,關閉明體及楷書字體是我首先會調整的設定之一,因為這兩個字體實在不適合在電腦上閱讀使用,但大多數狀況下他的優先權卻又高於其他字體,能夠關閉的字體則在 Font.app 中完成,如果為系統字型無法關閉,我則會把字型由 /Library/Fonts 複製到 ~/Library/Fonts 然後就能在 Font.app 中關閉但並沒有真的刪除字型,這個動作在 rootless 的限制下就無法完成,更別說像 XtraFinder 這類的插件也都無法正常執行。

Disable rootless

唯一的方式只有關閉 rootless,要關閉 rootless 需要進入 Recovery mode,未進入回復模式下執行的 nvram 都沒有作用,系統重新啟動後就會再複原。

1. 開機始按住 command-r 進入 recovery mode
2. 於選單中選擇 Utility > Terminal 開啟終端機
3. 執行 csrutil disable 關閉 SIP
4. 重新啟動

完成上述動作,就完成關閉 rootless。可以再執行 csrutil status 確認 SIP 的啟用狀況,有需要重新開啟時,重複上述動作,指令改為 csrutil enable 則可重新開啟。

Terminal 過程如下

-bash-3.2# csrutil

Modify the System Integrity Protection configuration.  All configuration changes apply to the entire machine. 
Available commands:

    clear
        Clear the exiting configuration.  Only available in recovery OS. 
    disable
        Disable the protection on the machine.  Only available in recovery OS. 
    enable
        Enable the protection on the machine.  Only available in recovery OS.
    status
        Display the current configuration. 

    netboot 
        add ﹤address﹥
            Insert a new IPv4 address in the list of allowed netboot sources.
        list
            Print the list of allowed NetBoot sources. 
        remove ﹤address﹥
            Remove an IPv4 address from the list of allowed NetBoot sources. 

-bash-3.2# csrutil disable
Successfully disabled system integrity protection. please restart them machine for the changes to take effect.

有興趣更進一步瞭解 SIP 可以觀看 WWWDC2015 Security and Your Apps 影片]]>
ethan@creativecrap.com
iOS9 and hardware keybaord https://creativecrap.com/blog/murmur/ios9-and-hardware-keybaord.html https://creativecrap.com/blog/murmur/ios9-and-hardware-keybaord.html Sun, 20 Sep 2015 08:00:00 +0800 It's another buzy session for app store, but iOS9 and hardware keyboard support is coming. Any new It's another buzy session for app store, but iOS9 and hardware keyboard support is coming.

Any new feature requests? now it's a good time.

]]>
ethan@creativecrap.com
All about table https://creativecrap.com/article/tutorials/all-about-table.html https://creativecrap.com/article/tutorials/all-about-table.html Thu, 02 Jul 2015 08:00:00 +0800 圖形使用界面的網頁開發軟體都支援直接畫表格,但不適當的使用方式,相對也多產生了不必要的處理時間。當然這樣的問題不單只有表格還有許多的因素,但本篇先著重在表格常見的錯誤使用方式,及應避免的況狀。

當被問到某個頁面或是活動案要需要多久時間,多數我會回答看頁面怎麼製作的,其中一個影響的因素即是是否有使用表格,及如何使用表格。 圖形使用界面的網頁開發軟體都支援直接畫表格,但不適當的使用方式,相對也多產生了不必要的處理時間。當然這樣的問題不單只有表格還有許多的因素,但本篇先著重在表格的問題。表格本身並沒有問題,而是不當的使用壞了他的名聲。

表格應用上常見的問題

錯誤的連結標籤位置

<table>  
<tr>  
    <td><a href="#link1">cell1</a></td>  
    <a href="#link2"><td>cell2</td></a>  
</tr>  
<a href="#link3">  
<tr>  
    <td>cell3</td>  
    <td>cell4</td>  
</tr>  
</a>  
</table>  
cell1 cell2
cell3 cell4

這個表格有三個連結標籤的表現方式,但其中只有 #link1 是正確的,其餘二個是錯誤的。#link2,#link3 分別想達到,整個 <td> 及整列 <tr> 都可以點擊,而非只有欄位中的內容才能點擊。

首先 <tr> 的角色是 <td> 的容器,用來表達不同的 <td> 之間的關係,算是一種無實體不可視的標籤,所以在 <table><tr><td> 之間不應該出現任何內容,所有錯誤的內容都會被移出,如果你試著在中間中輸入任何文字,會發現這些文字會出現在表格式外,這是因為在正確的 DOM Tree 中,會是下面的架構。

dom tree diagram

你或許會問如果這是錯誤的用法,那麼顯示的 html 結果也是不正確的,那麼怎麼還會發生有人錯誤使用的問題。這就要歸咎於 ie 了,因為僅有 ie 可以容許這樣的寫法。

正確的做法應該使用 onClick event,同樣的表格加上了 onclick 事件處理。

<table>  
<tr>  
    <td><a href="#link1">cell1</a></td>  
    <td onclick="alert('#link2');">cell2</td>  
</tr>  
<tr onclick="alert('#link3');">  
    <td>cell3</td>  
    <td>cell4</td>  
</tr>  
</table>  
cell1 cell2
cell3 cell4

當然依所要達到的結果不同,也可以使用 Javascript 來處理這些事件,可以讓 html 的內容更為乾淨更容易瀏灠。

$('td').click(function() {  
    console.log('td clicked!');  
});  

$('tr').click(function() {  
    console.log('tr clicked!');  
});  

當然在實務上,你會需要更多的條件或參數來輔助,上面的範例同時對 <tr><td> 都做了 click 事件處理,而 <tr><td> 的容器,所以點擊任何一個 <td>,都同時觸發該 <td> 所屬的 <tr> 的點擊事件,也就是 td clicked!tr clicked! 兩個結果都會出現。當然這也包含 <td> 中的內容,點擊 #link1 也同樣都會觸發兩個 click 事件。

我們可以加上不同 css 類別來區分不同的目標元件,再以 data 標籤來傳遞所需要的參數。

<table>  
<tr>  
     <td><a href="#link1">cell1</a></td>  
     <td class="cell" data-link="#link2">cell2</td>  
</tr>  
<tr class="row" data-link="#link3">  
     <td>cell3</td>  
     <td>cell4</td>  
</tr>  
</table>  

<script>  
$('td.cell').click(function() {  
    console.log('td:' + $(this).data('link'));  
});  

$('tr.row').click(function() {  
    console.log('tr:' + $(this).data('link'));  
});  
</script>  

經過這樣分類之後,就不會有重複的事件處理,如此一來點擊 cell3 或 cell4 會只會顯示 tr:#link3,而點擊 cell2 則顯示 td:#link2,當然點了 cell1 就直接連結到指定的 #link1 連結。

Missing ul, ol tag

同樣的規則也存在 <ul><ol><dl> 等條列式標籤中。任何的 <li><dd> 都不應該獨立存在,一定要包含在 <ul><ol><dl> 之中,所有的內容也該只能存在 <li> 之內,<ul><ol><dl><li> 之間不應該有任何其他內容。

這些也是常見錯誤:

a 放於錯誤的位置  
<ul>  
    <a href="#"><li>list1</li></a>  
</ul>  

缺少 ul, ol 標籤  
<li>list1</li>  
<li>list2</li>  
<li>list3</li>  

避免過於複雜的表格

有時因為資料的特性,表格也會相較的複雜而使用了 colspanrowspan 屬性來合併數個 <td> 變成跨欄或跨列的欄位,在以顯示資料為主的表格。不過這邊要提的並不是指資料表格,而是做為 layout 使用的表格。

的確在 css3 的時代,已不應該再使用 <table> 來做 layout 使用,但只要使用得宜並非不可以,況且仍有許多頁面仍使用 Dreamweaver 來畫表格,或使用 Photoshop 來直接輸出 html,都會出現這樣的問題。還有一個情況只能使用 <table> 的是 edm,因為大部份線上的 Email 系統會過濾信件內文,所以都不完整的支援 css,不同的平台過濾條件的差異性也不同,這種時候只有 <table> 是唯一的選擇。

分割成數個 Table

<table>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="3" rowspan="4"></td>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="4"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td colspan="7"></td>  
</tr>  
<tr>  
    <td>1</td>  
    <td>2</td>  
    <td>3</td>  
    <td>4</td>  
    <td>5</td>  
    <td>6</td>  
</tr>  
</table>  
1 2 3 4 5 6

這種狀況很常見,只有最後一列需要切成數欄,所以其他列便設定了 colspan 來合併成一欄,之後某個原因最後一列刪除了一欄,你也會需要更改其他列的 colspan 或是根本就忘了,因為 colspan 已超過最多欄數,所以顯示的結果看起來會相同。

把握一個簡單的原則,當需要合併的欄數或列數超過需要清點,或是使用的次數超過數次,這時候就應該要把一個大表格,拆成數個小表格使用。

<table>  
<tr><td></td></tr>  
<tr><td></td></tr>  
<tr><td></td></tr>  
<tr>  
<td>  
    <table width="100%">  
    <tr>  
        <td width="50%"></td>  
        <td width="50%">  
            <table width="100%">  
            <tr><td></td></tr>  
            <tr><td></td></tr>  
            <tr><td></td></tr>  
            <tr><td></td></tr>  
            <tr><td></td></tr>  
            <tr><td></td></tr>  
            <tr><td></td></tr>  
            </table>  
        </td>  
    </tr>  
    </table>  
</td>  
</tr>  
<tr><td></td></tr>  
<tr><td></td></tr>  
<tr>  
<td>  
    <table width="100%">  
    <tr>  
        <td>1</td>  
        <td>2</td>  
        <td>3</td>  
        <td>4</td>  
        <td>5</td>  
        <td>6</td>  
        <td>7</td>  
        <td>8</td>  
        <td>9</td>  
    </tr>  
    </table>  
</td>  
</tr>  
</table>  
1 2 3 4 5 6 7 8 9

把比較複雜或是可能會造成其他欄位需要合併的狀況的部份,另外製作成獨立的表格,原本的一個大表格,修改後則會變成一個大表格與三個小表格組成,乍看 html 變長了但彼此之間的相依性降低了,修改的彈性反而增加了許多,不用再擔心修改了某個欄位是否會影響其他欄位。

避免過度分散內容

<table>  
<tr>  
    <td><input type="radio"></td>  
    <td>Lorem ipsum</td>  
</tr>  
<tr>  
    <td colspan="2">Choose one or two options:</td>  
<tr>  
<tr>  
    <td></td>  
    <td>  
        <input type="checkbox"> option1<br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <input type="checkbox"> option2<br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <input type="checkbox"> option3<br>  
    </td>  
</tr>  
</table>  
Lorem ipsum
Choose one or two options:
option1
option2
option3

這樣的問題不一定只發生在使用表格時,只是因為軟體的使用習慣加上不瞭解 DOM 的關係,比較常發生在表格上。首先來提比較容易理解,但通常被忽略的 UI (User Interface)。當在使用 radio, checkbox 請務必配合 <label> 使用,這樣使用者在點擊時,只要點了相關的文字即可,不需要一定要點到 radio, checkbox 本身,除了這是一個直覺反應之外,通常選項的文字範圍也會比 radio, checkbox 本身大的許多而容易點擊。

因為在 radio 及該選項的文字,被區分在不同的 <td> 中,所以 <label> 必需要分開設定,所以你需要這麼做。

<tr>  
    <td><input type="radio" id="option1"></td>  
    <td><label for="option1">Lorem ipsum</label></td>  
</tr>  

當然這樣是正確的語法,也是正確的架構沒有錯,但請想像當表單上有數十個這樣的選項,你需要為每一個選項指定 id 名稱,請注意 id 是唯一值不可重複 (這也是常犯錯誤之一),每個選項需要指定 id 名稱兩次,這是個無聊又煩索的工作。

<tr>  
    <td></td>  
    <td><label><input type="radio"> Lorem ipsum</label></td>  
</tr>  

當相關的元件擺在一起時,只要簡單直接用 <label> 包住,則完成完全相同的效果,請看這樣省下了多少 html 代碼所使用的空間以及指定 id 名稱的時間。

再進一步,我們來看 checkbox 的部份,假設我們限制至少需要勾選一項,最多不可超過二項,檢查的部份只能使用 Javascript。說明方便起見,我們先把原先的 html 加上需要的名稱。

<tr>  
    <td></td>  
    <td>  
        <label><input type="checkbox" name="question[]" value="1"> option1</label><br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <label><input type="checkbox" name="question[]" value="2"> option2</label><br>  
    </td>  
</tr>  
<tr>  
    <td></td>  
    <td>  
        <label><input type="checkbox" name="question[]" value="3"> option3</label><br>  
    </td>  
</tr>  

指定元件名稱為 "question[]",因為 checkbox 是複選選項,在 Javascript, PHP 的命名習慣上,這樣會使得該變數在表單送出時,以陣列的方式存在來達到複數選項的目的。再以 Javascript 程式來檢查 "question[]" 的數量是否符合我們設定的條件。

// 找尋所有 input 元件,其類型為 checkbox 並且名稱為 "question[]",同時是已選取的項目  
var $questionElements = $("input[type=checkbox][name='question[]']:checked");  
if ($questionElements.length < 1 || $questionElements.length > 2 ) {  
    // 找到的項目的數量不足 1 或是大於 2  
    // 取消表單送出,顯示提示回饋  
    return false;  
}  

html 的架構算單純,檢查的程式也很簡單啊,你說。同樣的請想像你有數個這樣的內容,以及限制的數量條件或許不同,更或許有許多表單,那麼這段程式需要重複的出現多少次呢? 嗯哼!? Let's make it better, shall we?

<tr>  
    <td>  
        <p>Question 1:</p>  
        <div class="checkbox-group" data-min="1" data-max="2">  
            <label><input type="checkbox" name="question[1][]" value="1"> option1</label><br>  
            <label><input type="checkbox" name="question[1][]" value="2"> option2</label><br>  
            <label><input type="checkbox" name="question[1][]" value="3"> option3</label><br>  
            <label><input type="checkbox" name="question[1][]" value="4"> option4</label><br>  
        </div>  
    </td>  
</tr>  
<tr>  
    <td>  
        <p>Question 2:</p>  
        <div class="checkbox-group" data-min="2" data-max="5">  
            <label><input type="checkbox" name="question[2][]" value="1"> option1</label><br>  
            <label><input type="checkbox" name="question[2][]" value="2"> option2</label><br>  
            <label><input type="checkbox" name="question[2][]" value="3"> option3</label><br>  
            <label><input type="checkbox" name="question[2][]" value="4"> option4</label><br>  
            <label><input type="checkbox" name="question[2][]" value="5"> option5</label><br>  
            <label><input type="checkbox" name="question[2][]" value="6"> option6</label><br>  
        </div>  
    </td>  
</tr>  

同樣的還是保留了 <table> 相關的代碼在範例中。將不同的問題分別再包在另一個 <div> 中,並給予 "checkbox-group" 類別名稱,再分別設定所需要的其他參數,data-min 表示最少需選取項目, data-max 表示最多可選取項目。適當的將相關的屬性的項目放在一起,html 更容易閱讀相關的程式的撰寫也更單純。

// 先假設檢查會是通過的  
var valid = true;  

$(".checkbox-group").each(function() {  
    var $group = $(this);  
    var min = parseInt($group.data('min'), 10);  
    var max = parseInt($group.data('max'), 10);  

    var $elements = $group.find("[type=checkbox]:checked");  
    if ($elements.length < min || $elements.length > max) {  
       // 一互有發現不符合條件的,則設定檢查狀況是失敗的  
        valid = false;  
        // 脫離 $(".checkbox-group").each() 這個迴圈  
        // 不再往下做其他項目的檢查  
        return false;  
    }  
});  

if (!valid) {  
    // 找到的項目的數量不符合條件  
    // 取消表單送出,顯示提示回饋  
    return false;  
}  

先別害怕,程式如何撰寫並不是這裡的重點,這段程式與上面 html 所做的分類的邏輯完全相同。逐一對 .checkbox-group 類別的項目,依指定的 min, max 數值,檢查底下的 checkbox 選取的數量是否符合。注意到程式中完全沒有出現 checkbox 的名稱嗎? 因為在檢查的過程中,我們只要知道對象群組及條件,並不需要知道對象個別是誰,這段程式不但不需要先前的重複複製、貼上再修改名稱及數量,而且也容易重複使用不需要每次重新撰寫,即使選項的條件變更,也只需要修改 html 中的設定,不需要修改程式,前提當然是有 html 正確組織及配合的參數命名方式。

垃圾進垃圾出

正確的組織不在於語法的正確與否,也不是使用了 <div> 就比較高等,用 <table> 就是錯誤,而是在於將內容依屬性適當的群組化,這樣不但讓 html 代碼易讀性提高,需要修改時或是添加 css 設定時,都會更清楚及容易,當然有邏輯的分類也才能夠撰寫出良好的程式。

再一個表單的例子。有一批產品的資料,包含有圖片,名稱及簡短說明文字。這是一個非常常見的糟糕架構,牽扯到的層面也比較多一點,接下來會分別提到 html, css, php 三個部份。

<table>  
<tr>  
    <td>img1</td>  
    <td>img2</td>  
    <td>img3</td>  
    <td>img4</td>  
</tr>  
<tr>  
    <td>name1</td>  
    <td>name2</td>  
    <td>name3</td>  
    <td>name4</td>  
</tr>  
<tr>  
    <td>text1</td>  
    <td>text2</td>  
    <td>text3</td>  
    <td>text4</td>  
</tr>  
<tr><td colspan="4"></td></tr>  
<tr>  
    <td>img5</td>  
    <td>img6</td>  
    <td>img7</td>  
    <td>img8</td>  
</tr>  
<tr>  
    <td>name5</td>  
    <td>name6</td>  
    <td>name7</td>  
    <td>name8</td>  
</tr>  
<tr>  
    <td>text5</td>  
    <td>text6</td>  
    <td>text7</td>  
    <td>text8</td>  
</tr>  
<tr><td colspan="4"></td></tr>  
<tr>  
    <td>img9</td>  
    <td></td>  
    <td></td>  
    <td></td>  
</tr>  
<tr>  
    <td>name9</td>  
    <td></td>  
    <td></td>  
    <td></td>  
</tr>  
<tr>  
    <td>text9</td>  
    <td></td>  
    <td></td>  
    <td></td>  
</tr>  
</table>  
img1 img2 img3 img4
name1 name2 name3 name4
text1 text2 text3 text4
img5 img6 img7 img8
name5 name6 name7 name8
text5 text6 text7 text8
img9
name9
text9

要由程式產生表格,需要定義或計算表格的欄數及列數,照指定的順序輸出欄位及其內容,輸出的欄位數不滿一列定義欄位數時,需輸出空的的欄位,每完成一列輸出,依未輸出資料數判斷是否要產生間隔列。

比較複雜的部份在於如何依指定的順序輸出,因為這個表格並非每次處理一項產品,而是每項產品要分次輸出其內容。先依順輸出四項產品的 image 欄位,再於次列輸出 name 欄位,再次列輸出 description 欄位,至此完成四項產品的之後,再處理下四筆資料。

<table>  
<?php  
// 假設的資料來源  
$items = [  
    ['image' => 'img1', 'name' => 'name1', 'description' => 'text1'],  
    ['image' => 'img2', 'name' => 'name2', 'description' => 'text2'],  
    ['image' => 'img3', 'name' => 'name3', 'description' => 'text3'],  
    ['image' => 'img4', 'name' => 'name4', 'description' => 'text4'],  
    ['image' => 'img5', 'name' => 'name5', 'description' => 'text5'],  
    ['image' => 'img6', 'name' => 'name6', 'description' => 'text6'],  
    ['image' => 'img7', 'name' => 'name7', 'description' => 'text7'],  
    ['image' => 'img8', 'name' => 'name8', 'description' => 'text8'],  
    ['image' => 'img9', 'name' => 'name9', 'description' => 'text9'],  
];  

// 設定每列顯示的產品數量  
$numberOfItemsPerRow = 4;  

// 稍後在輸出產品內容時對應用的名稱  
$keysForItem = array('image', 'name', 'description');  
// 取得產品資料的欄位筆數,供稍後使用,本範例中是 3  
$numberOfValuesPerItem = count($keysForItem);  
// 取得資料總筆數,本範例中是 9  
$totalItems = count($items);  
// 以無條件進位計算表格的總列數,本範例中是 ceil(9 / 4) = 3  
$totalRows = ceil($totalItems / $numberOfItemsPerRow);  
// 用來讀取產品資料使用的索引  
$index = 0;  

// html 是由上到下,所以第一個迴圈會是需要輸出的列數的次數  
// 程式一般會由 0 開始計算,所以這裡的 $i 的結果分別會是 0, 1, 2 三次  
for ($i = 0; $i < $totalRows; $i++) {  

    // 每項產品要輸出的欄位次數,$j 的結果分別是 0, 1, 2 三次  
    for ($j=0; $j < $numberOfValuesPerItem; $j++) {  

        // 計算產品資料索引,這裡會執行了兩個迴圈的次數,也就是 3 x 3 = 9 次  
        // 每次起始會是該產品列的第一項產品,所以 $index 的結果會是  
        // ($i = 0) 0, 0, 0  
        // ($i = 1) 4, 4, 4  
        // ($i = 2) 8, 8, 8  
        $index = $i * $numberOfItemsPerRow;  

        // 取得本次要輸出產品資料的欄位名稱,$key 所有結果會是  
        // ($i = 0) image, name, description  
        // ($i = 1) image, name, description  
        // ($i = 2) image, name, description  
        $key = $keysForItem[$j];  

        echo "<tr>";  

        // 由左到右,開始準備輸出每個表格欄位  

        // 每列產品數,$z 的結果分別是 0, 1, 2, 3  
        // 這裡一共會執行 3 x 3 x 4 = 36 次  
        for ($z=0; $z < $numberOfItemsPerRow; $z++) {  

            // $index 索引分別在 $i, $j, $key 時的所有結果  
            // ($i = 0, $j = 0, $key = image)           0, 1, 2, 3  
            // ($i = 0, $j = 1, $key = name)            0, 1, 2, 3  
            // ($i = 0, $j = 2, $key = description)     0, 1, 2, 3  
            // ($i = 1, $j = 0, $key = image)           4, 5, 6, 7  
            // ($i = 1, $j = 1, $key = name)            4, 5, 6, 7  
            // ($i = 1, $j = 2, $key = description)     4, 5, 6, 7  
            // ($i = 2, $j = 0, $key = image)           8, 9, 10, 11  
            // ($i = 2, $j = 1, $key = name)            8, 9, 10, 11  
            // ($i = 2, $j = 2, $key = description)     8, 9, 10, 11  

            if (isset($items[$index])) {  
                // 若有產品資料時,則輸出第 $index 筆資料,欄位名稱為 $key 的內容  
                echo "<td>" . $items[$index][$key] . "</td>";  
            }  
            else {  
                // 若無資料,則輸出空格  
                echo "<td></td>";  
            }  
            // 索引累加 1  
            $index++;  
        }  
        echo "</tr>";  
    }  

    // 每輸出完一輪產品列,即 4 項產品,判斷是否要輸出間隔用的空列  
    // $index 分別會是 3, 7, 11  
    if ($index < $totalItems) {  
        echo '<tr><td colspan="' . $numberOfItemsPerRow. '"></td></tr>';  
    }  
}  
?>  
</table>  

如果你不熟悉寫程式,這段內容會看起來複雜許多,但幾個小時過後我也會不容易看懂。因為這段程式的邏輯是為了產生表格,而不是一般在處理資料時的邏輯,因此幾乎每次都會需要花點時間重看程式的做法,才會知道該段程式的目的。如何撰寫程式並不是本篇主題,但應仍可發現主要在輸出 html 內容的程式只有 5 行,並且迴圈總計跑了 36 次,而這個範例只有 9 筆資料。

錯誤的例子看完了,那麼就來看正確應該怎麼做。雖然上面已經提過 <table> 並不適合拿來做排版,但是這篇主要還是以 <table> 為主的文章,所以還是會使用 <table> 但是會使用在正確的地方。

<div class="items-wrapper">  
    <div class="item-box">  
        <table>  
        <tr><td>image</td></tr>  
        <tr><td>name</td></tr>  
        <tr><td>text</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>image</td></tr>  
        <tr><td>name</td></tr>  
        <tr><td>text</td></tr>  
        </table>  
    </div>  
</div>  

首先我們拉好所以需要的 html 架構,每項產品的資料放置於 <table> 內,外面再以 div.item-box 包住,這樣可以讓架構更清楚一點,也容易使用 css 來做其他的樣式排版,是不是簡單又清楚許多呢?

<div class="items-wrapper">  
    <?php foreach($items as $item): ?>  
    <div class="item-box">  
        <table>  
        <tr><td><?php echo $item['image']; ?></td></tr>  
        <tr><td><?php echo $item['name']; ?></td></tr>  
        <tr><td><?php echo $item['description']; ?></td></tr>  
        </table>  
    </div>  
    <?php endforeach; ?>  
</div>  

加上程式之後幾乎還是維持原 html 的內容,程式部份只有 5 行,其中 3 行是輸出資料,顯示會輸出的內容即使是看原始碼也一目瞭然。因為我們只需要在對應的地方顯示資料,而不需要由程式來控制排版或 html 的輸出,瀏灠器直接開啟頁面,或是經過其他編輯軟體預覽時,也不會因為無法正確執行 php 程式而造成頁面破碎的問題,接下來我們只要再使用 css 來控制我們想要的排版即可。

.items-wrapper {  
    width:100%;  
}  
.items-wrapper:after {  
    content:"";  
    display:table;  
    clear:both;  
}  
.item-box {  
    width:25%;  
    float:left;  
    margin-bottom:10px;  
}  

簡單的設定以 .items-wrapper 為最大可用寬度,.item-box 每項可使用的寬度為其父元件 .items-wrapper 寬度的 25% 並向左靠齊,這樣可以達到每排 4 項產品,若要調整每排的數量,只要調整可使用的寬度即可,例如改為 33% 則每排三項。 margin-bottom 用來取代 <tr><td colspan="4"></td></tr> 每排的間隔。 最後,因為 .item-box 是往左靠齊的,所以當產品項目沒有剛好填滿整個 .items-wrapper 空間時,在這之後的內容仍會因為置左的影響而跑上來,:after pseudo 的部份則正是讓置左的效果失效。

最後的結果及 html 原始碼會像這樣

<div class="items-wrapper">  
    <div class="item-box">  
        <table>  
        <tr><td>img1</td></tr>  
        <tr><td>name1</td></tr>  
        <tr><td>text1</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img2</td></tr>  
        <tr><td>name2</td></tr>  
        <tr><td>text2</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img3</td></tr>  
        <tr><td>name3</td></tr>  
        <tr><td>text3</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img4</td></tr>  
        <tr><td>name4</td></tr>  
        <tr><td>text4</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img5</td></tr>  
        <tr><td>name5</td></tr>  
        <tr><td>text5</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img6</td></tr>  
        <tr><td>name6</td></tr>  
        <tr><td>text6</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img7</td></tr>  
        <tr><td>name7</td></tr>  
        <tr><td>text7</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img8</td></tr>  
        <tr><td>name8</td></tr>  
        <tr><td>text8</td></tr>  
        </table>  
    </div>  
    <div class="item-box">  
        <table>  
        <tr><td>img9</td></tr>  
        <tr><td>name9</td></tr>  
        <tr><td>text9</td></tr>  
        </table>  
    </div>  
</div>  
<p>end of products</p>  
img1
name1
text1
img2
name2
text2
img3
name3
text3
img4
name4
text4
img5
name5
text5
img6
name6
text6
img7
name7
text7
img8
name8
text8
img9
name9
text9

end of products

Please

Being a web designer you may not need to expert at javascript or php, but still it's your job to make properly html and css contents, otherwise you are just a graphic designer or people who knows photoshop, it's the same as web developers. And most of these problems are not technical but it cause technical issues which should be prevent from the begining.

]]>
ethan@creativecrap.com
relaunch 2015 https://creativecrap.com/blog/misc/relaunch-2015.html https://creativecrap.com/blog/misc/relaunch-2015.html Wed, 17 Jun 2015 08:00:00 +0800 整個重翻新了一輪,應該是第 21 或 22 版了 整個重翻新了一輪,應該是第 21 或 22 版了

]]>
ethan@creativecrap.com
羊年 https://creativecrap.com/blog/murmur/year2015.html https://creativecrap.com/blog/murmur/year2015.html Sat, 28 Feb 2015 19:41:59 +0800 雖然進度很緩慢,不過也還是有在進行,現在又卡在 Swift 1.2 可能會在四月中後才有可能進到 GM 的階段,可能會需要再等二個月,或是另外建一個 branch 來進行,需要再考慮一下。

雖然進度很緩慢,不過也還是有在進行,現在又卡在 Swift 1.2 可能會在四月中後才有可能進到 GM 的階段,可能會需要再等二個月,或是另外建一個 branch 來進行,需要再考慮一下。

關於 zhim! extended 的未來,在 iOS keyboard extension 尚未支援硬體鍵盤之前,還是會以現行的方式存在。

]]>
ethan@creativecrap.com
one step back, one step forward https://creativecrap.com/blog/murmur/one-step-back-one-step-forward.html https://creativecrap.com/blog/murmur/one-step-back-one-step-forward.html Tue, 09 Sep 2014 06:27:51 +0800 iOS8 終於開放了系統鍵盤給開放者,但實際上仍有非常多的限制,以及穩定性並不佳。zhim! extended 雖然已經加上了支援 iOS8... Custom Keyboard for iOS8

iOS8 終於開放了系統鍵盤給開放者,但實際上仍有非常多的限制,以及穩定性並不佳。
zhim! extended 雖然已經加上了支援 iOS8 custom keyboard 的支援,經過了 5 個 iOS8 beta 版本的測試,最終我還是覺得本質上的目的性不同,因此決定不在 zhim! extended 上直接提供 keyboard extension,而會以另外的 app 來提供單一性的目的。

另外也會改以 Swift 開發,比對在 Objective-C 上遇到的效能問題是否能在 Swift 上解決。
我想類似的 app 應該也會比較多了,時間上也就可以慢慢來了 (誤)

]]>
ethan@creativecrap.com
zhim! extended v2.6 - iPhone, iPad 中文輸入工具 https://creativecrap.com/app/zhim-extended.html https://creativecrap.com/app/zhim-extended.html Sun, 08 Jun 2014 12:05:13 +0800 zhim! 是支援各式中文輸入法的記事本。不管是倉頡、大易、嘸蝦米、輕鬆輸入法,倚天注音,拼音、簡易、包含注音等等,只要有該輸入法的字根表,皆可轉換為 zhim! 所使用的字典檔,便可以在 zhim! 中使用該輸入法輸入中文。

Input methods

zhim! is a text editor with Chinese input method framework, a variety of tables are available, including Array(行列), Boshiamy(嘸蝦米), Cangjie(倉頡), Dayi(大易), easy(輕鬆), Eten(倚天注音), Hsu(許氏注音), Zhuyin(注音), among others.

The tables can easily be edited or imported from CIN plugin format data table directly in-app or using online converting tool.

Eash regular and symbol keyboard supports up to 5 row keyboard layouts, and keyboard layouts can be edited for each input method.

Helper

Choose any character or phrase from candidate bar by tap it or candidate key defined by input method. A trick for the composing buffer, you can use it for input English without switching keyboards.

Ever forgot how to spell a word or try to fix a typo? By select a word from editor and choose Find by pronounce, it will shows words with the same pronounce. Or find phrase to look for phrases starts with the selection.

Share and Sync

zhim! share text with other apps by copy and paste from clipboard, with Auto-Copy on, you just simply quit zhim! by home button then launch another app and paste text from clipbaord.

You can launch Line, WeChat, Map apps quickly from action button, or add item to Calendar, Reminder, composing to twitter, facebook directly without leaving the app.

Once you get used to it, it's actually very handy.

You can save as many as you want documents on device, to benefit for multiple devices, you can sync documents via iCloud, Dropbox and Evernote.

Add new input method

Create new input method directly from device is much easier, also improve cin-format data table importing performance.

There's no more certain filename limits, any text file will be accept. Even better you can choose download file from web during the import process.

Watch the screencast of How to install new input method directly from your device.

More screenshots

To learn more about zhim!'s dictionary

Read more...

Import cin plugin by online converter

Read more...

Compatibility: Requires iOS 6.0 or later. Compatible with all iOS devices.

App Store Badge

]]>
ethan@creativecrap.com
Install iLyricsCloud on OpenShift https://creativecrap.com/article/tutorials/install-ilyricscloud-on-openshift.html https://creativecrap.com/article/tutorials/install-ilyricscloud-on-openshift.html Sat, 31 May 2014 19:21:00 +0800 將 iLyricsCloud 架在 OpenShift 的過程教學,以及安裝 iLyricsCloud 所需更改的設定說明。... 在 OpenShift 上建立 iLyricsCloud

首先您需要有 OpenShift 的帳號,以及已設定好 SSH Key 以能夠進行遠端登入。
過程中更新檔案會使用到 git,如果不想使用 git 仍是可以使用 FTP 的方式存取檔案,但皆不包含在本篇內容中,關於 OpenShift SSH, Git 請參考官方網站。

https://www.openshift.com/developers/remote-access
http://git-scm.com

建立 Application

在 OpenShift 中,您可以簡單的把 Application 當做是一個獨立的 Web server,而我們要在這個空的 Web server 上建立 PHP5/MySQL 的環境。

Applications Panel 中選擇 Add Application 來開始建立。

步驟 1,請選擇平台 PHP 5.4 或任何 PHP 5 以上版本皆可。

步驟 2,app 的基本設定。Public URL 會是這個 app 的名稱,也會是 OpenShift 產生的網址,填上想要的名字即可, 圖例建立的名稱是 ilyrics。OpenShift 可直接幫你將原始碼從 Git repo 拉一份過來,所以這邊接直貼上 iLyricsCloud 位於 Github 的網址 https://github.com/ethanliu/iLyricsCloud.git 可以省下一些下載上傳的動作。

步驟3,即 app 建立完成,會顯示這個 app 相關的 git 簡易入門,這些指令待會我們也會用到,但這裡可以先不予理會,直接往下進到 application overview page。


接著要安裝的是 MySQL 所以請點選 Add MySQL 5.5 進行安裝。

安裝完成將會看到 MySQL 的連線資訊,包括資料庫名稱、帳號、密碼等,一般狀況下你會需要先記下這些資料,不過配合 OpenShift 我們有更便利的使用方式,可以先不需要記下這些資料。

回到 overview 查看一下,確認有我們要的 PHP 及 MySQL 的環境了,請複製右上 Source Code 裡面的那一長串 ssh://xxxxx 的網址,至此我們已經完成 OpenShift 端的工作。

設定 iLyricsCloud

接下來便是 iLyricsCloud 的相關設定,首先我們需要將剛剛建立好的 app 的原始碼拉回到我們的電腦中,開啟命令列視窗,執行以下指令,即步驟 3 提示的內容。

# git clone [OpenShift Source Code 網址] [目的資料夾]  
> git clone ssh://5389bc7c5973...rhcloud.com/~/git/ilyrics.git/ ~/Documents/ilyrics-demo  

這表示將 OpenShift 上的原始碼下載到 ~/Documents/ilyrics-demo 這個資料夾,如果不設定則會建立 APPNAME 資料夾,依設定的名稱而定,執行過程中會看到如下的訊息。

Cloning into 'ilyrics-demo'...  
remote: Counting objects: 425, done.  
remote: Compressing objects: 100% (171/171), done.  
remote: Total 425 (delta 242), reused 425 (delta 242)  
Receiving objects: 100% (425/425), 181.18 KiB | 177.00 KiB/s, done.  
Resolving deltas: 100% (242/242), done.  

完成後,開啟 ~/Documents/ilyrics-demo 資料夾,就會看到 iLyricsCloud 的程式碼,與直接從 Github 上下載的版本相同。

在這個資料夾中,將 config.sample.php 更名,或是複製並命名為 config.php,接著開啟 config.php 進行編輯,安全起見,我們先把預設的 iLyricsCloud 帳號密碼做更換

define('ADMIN_USER', 'admin');  
define('ADMIN_PASS', 'admin');  

將兩個 "admin" 更改為其他內容。再來更新資料庫的設定,請找到下面這兩行

define('DATABASE_DSN', 'sqlite:' . dirname(__FILE__) . '/cache/cache.db');  
// define('DATABASE_DSN', 'mysql:host=127.0.0.1;port=3306;dbname=;user=;password=;');  

更換成下面的內容,直接複製則上即可。這裡會看到我們改使用 mysql 而不是使用 sqlite,並且使用 OpenShift 的環境便數來取得資料庫的帳號資料,而並非直接設定。

// define('DATABASE_DSN', 'sqlite:' . dirname(__FILE__) . '/cache/cache.db');  
  
$host = getenv('OPENSHIFT_MYSQL_DB_HOST');  
$port = getenv('OPENSHIFT_MYSQL_DB_PORT');  
$dbname = getenv('OPENSHIFT_APP_NAME');  
$user = getenv('OPENSHIFT_MYSQL_DB_USERNAME');  
$passwd = getenv('OPENSHIFT_MYSQL_DB_PASSWORD');  
  
define('DATABASE_DSN', "mysql:host={$host};port={$port};dbname={$dbname};user={$user};password={$passwd};");  

{$host} 等 5 項也可以直接填入實際的內容。存檔後,我們要準備把更改過的檔案再上傳回 OpenShift,請再回到剛剛的命令視窗。

> git add config.php -f  
> git commit -m "my config.php"  
  
[master 8a05599] my config.php  
 1 file changed, 46 insertions(+)  
 create mode 100644 config.php  
  
> git push  
  
Counting objects: 4, done.  
Delta compression using up to 2 threads.  
Compressing objects: 100% (3/3), done.  
Writing objects: 100% (3/3), 824 bytes | 0 bytes/s, done.  
Total 3 (delta 1), reused 0 (delta 0)  
remote: Stopping MySQL 5.5 cartridge  
remote: Stopping PHP 5.4 cartridge (Apache+mod_php)  
remote: Waiting for stop to finish  
remote: Waiting for stop to finish  
remote: Building git ref 'master', commit 8a05599  
remote: Preparing build for deployment  
remote: Deployment id is 5a066d98  
remote: Activating deployment  
remote: Starting MySQL 5.5 cartridge  
remote: Starting PHP 5.4 cartridge (Apache+mod_php)  
remote: Application directory "/" selected as DocumentRoot  
remote: -------------------------  
remote: Git Post-Receive Result: success  
remote: Activation status: success  
remote: Deployment completed with status: success  
To ssh://5389bc7c5973...@ilyrics-creativecrap.rhcloud.com/~/git/ilyrics.git/  
   ceb7691..8a05599  master -> master  

更新完成之後,我們就可以打開 http://ilyrics-creativecrap.rhcloud.com/admin/ 也就是一開始建立的 Public URL 然後使用剛剛更改後的帳號密碼登入 iLyricsCloud 的管理界面,此時會看到 Install database 的提示字樣,如果沒有看到的話,可能是上面更改的內容有錯,請再重複上述的動作確認修改的內容正確,點擊 Install 後,成功則會看到 Install success 就完成了。可以點選單的 Search 打開搜尋界面,試試看搜尋應該就會顯示找到的歌詞或專輯圖片網站。

iLyricsWidget 設定的 iLyricsCloud 網址填入 http://ilyrics-creativecrap.rhcloud.com/q.php 就可以開始使用自動搜尋的功能了。


Summary

本篇因為是偵對在架在 OpenShift 上所以有些設定作業,實際上安裝 iLyricsCloud 僅需要更改資料庫設定的部份。文中的 ilyrics-creativecrap.rhcloud.com 及 app 名稱 ilyrics,請替換成您的資料不要直接使用。
若不想使用 Git 在步驟 2 的 Source Code 即可不用填寫,但需自行至 https://github.com/ethanliu/iLyricsCloud 下載原始碼,並透過 FTP 或其他方式上傳,包含後續更新檔案等等。

]]>
ethan@creativecrap.com
Passing parameters to Facebook Tab https://creativecrap.com/article/tutorials/passing-parameters-to-facebook-tab.html https://creativecrap.com/article/tutorials/passing-parameters-to-facebook-tab.html Wed, 25 Dec 2013 20:31:02 +0800 In most case, we simply need to pass some parameters through URL, it works on Facebook App but not F... fb-appdata.png

Passing parameters to Facebook Tab

In most case, we simply need to pass some parameters through URL, it works on Facebook App but not Facebook Tab, somehow Facebook blocks certain behavior on Facebook Tab.

To achive the purpose, we need to follow the rules by Facebook

https://www.facebook.com/[pagename]?v=app_[appid]&app_data=[parameters]

app_data is the only parameter will pass to your landing page, depending on what you assigned in facebook apps dashboard, and it's construct by json and encode by base64. On your landing page, you will receivce $_POST['signed_request'] contains informations we need.

$_POST['signed_request']

Fore example, we have a link //www.facebook.com/myappname?v=app_123456789&app_data=foo%3Dbar, then you get something looks like this.

{  
    "algorithm": "HMAC-SHA256",  
    "app_data": "foo=bar",  
    "issued_at": 1389437476,  
    "page": {  
        "id": "123456789",  
        "liked": true,  
        "admin": true  
    },  
    "user": {  
        "country": "tw",  
        "locale": "en_US",  
        "age": {  
            "min": 21  
        }  
    }  
}  

The parser

Here is the parser, it decode and returns the object specified by name. Since I know the app_data will always be query string, so let's parse it too.

function getSignedRequestData($name) {  
    $signed_request = explode('.', $_POST['signed_request']);  
    $json = json_decode(base64_decode($signed_request['1']), true);  
    if (!$name) {  
        return $json;  
    }  
    else if ($name === 'app_data') {  
        parse_str($json[$name], $data);  
        return $data;  
    }  
    return $json[$name];  
}  

A complete example

Say we have google anatyics tracking parameters and custom parameters. This is how you generate the valid URL

// Googla Analytics Tracking  
$ga = "utm_campaign=sample&utm_medium=cpc&utm_source=facebook";  
// custom parameters, notice the route, we need to encode it first  
$query = "foobar=lorem&route=" . urlencode("page2.php?fruit=apple&color=green");  
  
// your facebook app data  
$app_name = 'myapp';  
$app_id = '123456789';  
  
// facebook tab URL  
$link = "https://www.facebook.com/{$app_name}?v=app_{$app_id}&app_data=";  
$link .= urlencode($query . '&' . $ga);  
  
// add GA parameters as normal query string  
$link .= '&' . $ga;  

And this is what you get for the URL.

https://www.facebook.com/myapp?v=app_123456789&app_data=foobar%3Dlorem%26route%3Dpage2.php%253Ffruit%253Dapple%2526color%253Dgreen%26utm_campaign%3Dsample%26utm_medium%3Dcpc%26utm_source%3Dfacebook&utm_campaign=sample&utm_medium=cpc&utm_source=facebook  

Noticed I add $ga twice? one in app_data and one as normal query string with current URL. Well, facebook Tab accepts Google Trackers so it's pretty safe add them to query string, but it will not pass it to your page if you need those variables you have to add it to app_data as well.
To get the app_data we call the parser function by getSignedRequestData('app_data') and it returns an array and ready to use.

Array  
(  
    [foobar] => lorem  
    [route] => page2.php?fruit=apple&color=green  
    [utm_campaign] => sample  
    [utm_medium] => cpc  
    [utm_source] => facebook  
)  

Reference

Page Tab Tutorial - Facebook developers
Tracking Facebook Ads in Google Analytics

]]>
ethan@creativecrap.com
How to reisze Facebook Tab iframe size https://creativecrap.com/article/tutorials/how-to-reisze-facebook-tab-iframe-size.html https://creativecrap.com/article/tutorials/how-to-reisze-facebook-tab-iframe-size.html Wed, 25 Dec 2013 19:53:51 +0800 The best way to auto resize the iframe.... fb-autogrow.png

Facebook Tab size

Facebook has 2 size option for canvas, one is fluid and the other is fixed with specified height.
Neither way is not auto resize the iframe by its content, and there is a method FB.Canvas.setAutoResize to solve the problem, but when "AutoResize" is not really resizing the height of iframe by its content height, Facebook decide to rename it ot FB.Canvas.setAutoGrow.

What I mean by not resizing the height correctly, because what the fucntion does is make the iframe height enough for current content, but when the content changed, and the height is smaller then previous, FB.Canvas.setAutoGrow will not shrink the size for you, that's why it rename to AutoGrow not AutoResize.

The code

Back to the topic, so how do we make it works like auto resize.
A simple solution is we make the size smaller at first, then fire FB.Canvas.setAutoGrow to let Facebook decide how height it should be.

window.fbAsyncInit = function() {  
    FB.Canvas.setSize({height:600});  
    setTimeout(function() {  
        FB.Canvas.setAutoGrow();  
        setTimeout(function() {  
            FB.Canvas.setAutoGrow(false);  
        }, 500);  
    }, 500);  
};  

Why call it twice

FB.Canvas.setAutoGrow take a parameter as interval, yes it runs repeatly by a giving time. So the second FB.Canvas.setAutoGrow is simply to stop the timer.

If your content is dynamically changes, something like loading content via ajax call, then you may not need to stop the timer, depending on what you need.

Reference

FB.Canvas.setAutoGrow

]]>
ethan@creativecrap.com