我們的程式功能越來越多,
用到的快速鍵也越來越多。
有時連我自己也快忘記,
想做某件事應該按下什麼組合鍵了。
畢竟人的大腦容量有限,
記東西實在不是大腦最擅長的事,
如果可以的話,
我寧可讓大腦多騰出一下空間,
多去做一些理解、思考的工作。
方便而適時的提醒,是很重要的。
如果可以像「便條紙」那樣,隨時給點提示,
那肯定是件好事。
各位還記得嗎?
瀏覽器外掛有三個遊樂場,
此前我們一直都在網頁的【原始內容】裡玩耍,
今天就來善用一下【彈出頁面】,
把它當成「便條紙」,用來顯示快速鍵的提示訊息吧。
首先,我們要在 manifest.json 裡宣告一下,
這個外掛準備用到 popup.html 彈出頁面。
"action": {
"default_popup": "popup/popup.html"
}
接著我們建立一個 popup 子目錄,
準備後續用來存放各種 popup 相關檔案,
然後在裡頭建立一個 popup.html。
另外,也可以建立 popup.js 和 popup.css,
分別用來存放【彈出頁面】的 css 和 js 程式碼。
我們可以按照自己的需求,編寫 popup.html 檔案的內容。
其中特別要提醒的是,如果沒有特別指定 charset="utf-8",
中文顯示就會出現一堆亂碼。
解法也很簡單。
只要在 ... 裡頭添加一行設定即可:
<meta charset="utf-8">
我們目前的 popup.html 內容如下:
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css">
<script type="text/javascript" src="popup.js"></script>
</head>
<body>
<h1>更好的翻譯</h1>
<h3>快速鍵說明:</h3>
<div>
<ul id="hotkey_desc">
<li>Alt+1:進行分句,並備份原文</li>
<li>Alt+2:(請先進行 Google 翻譯)自動滾動頁面</li>
<li>Alt+3:備份譯文(然後請關閉 Google 翻譯)</li>
<hr/>
<li>Alt+上:切換原文、譯文(請關閉 Google 翻譯)</li>
<li>Ctrl+Shift+點擊:編輯譯句</li>
<li>Ctrl+Enter:完成譯句編輯</li>
</ul>
</div>
</body>
</html>
這樣就可以了。
重新載入外掛之後,
只要點擊網址列旁邊的外掛圖標,
就會彈出一個頁面,
裡頭正是我們剛才所撰寫的內容。
在 popup.html 裡填寫快速鍵說明時,
我忍不住地想,
這是很有效率的做法嗎?
如果將來添加其他快速鍵,
我會記得到這裡添加說明嗎?
後來我想了一下,
如果將來要添加新的快速鍵,就要做下面這些事情:
每添加一個快速鍵,就要到不同地方進行不同的處理,
這其實是很麻煩的事。
如果可以集中在一處,
再由程式碼去處理各處所需的變更,
管理起來肯定比較方便。
以這裡為例,
我們可以維護下面這樣的一個資料變數:
hotkeys = {
'Alt1': {
desc: '進行分句,並備份原文',
handler: Alt1,
},
'Alt2': {
desc: '(請先進行 Google 翻譯)自動滾動頁面',
handler: Alt2,
},
'Alt3': {
desc: '備份譯文(然後請關閉 Google 翻譯,或重新載入頁面)',
handler: Alt3,
},
......
}
如此一來,
content.js 可根據這裡的資料,
判斷快速鍵應該執行哪個處理函式,
popup.html 也可以根據它,
判斷該顯示哪些快速鍵說明。
這樣一來,將來要添加新的快速鍵,
只要來填寫這個資料變數的內容,
其他該做的事就會依序完成了。
而且,由於這個資料變數中,有許多之前留下的格式範例,
所以添加新資料時,也可以作為參考,
而不至於漏東漏西。
這種做法還有另一個好處,
就是可以用「索引」方式取代 if...else if... 的做法。
我們就從 document.onkeydown 的事件處理函式開始著手好了。
一開始,我們先根據事件 e 裡的資訊,
建立一個可用來表示快速鍵組合的字串:
hotkey_str = ((e.ctrlKey)?'Ctrl':'') + ((e.altKey)?'Alt':'') + ((e.shiftKey)?'Shift':'') + e.key
接著只要根據這個字串,檢查是否有相應的處理函式,
若有就直接執行即可。
if (hotkey_str in hotkeys) {
hotkeys[hotkey_str].handler();
}
這樣一來,我們原本用一堆 if...else if...else if... 來判斷快速鍵的做法,
就會變得極為簡潔。
當然,這需要搭配之前那個全域變數,
然後再針對各個 handler,寫下相應的程式碼。
乍看之下,
這只是把原本在 document.onkeydown 裡做的事,
移到 hotkeys 這個變數裡頭。
但仔細想想,
這種做法不但可以用索引直接找到處理函式,
還可以找到相應的說明。
如果想找到相應的其他東西,
這個架構也可以做到。
直接用索引找到需要的東西,
這種做法遠比一堆 if...else if...else if... 合理多了。
你想想,如果要去班上找某個人,
我們都應該是直接到班上喊名字,這個人很快就可以找到了。
至於 if...else if...else if... 的做法,
就好像每次都要從第一排第一個人、第二個人...
逐一詢問「你是不是某某某呀?」
就算電腦速度再快,也不應該浪費在這種奇怪的做法上才對。
當然,原本的做法還是比較常見、可讀性也還不錯。
不過,新的做法就像填寫制式表格,
使用者應該不會不熟悉才對。
仔細想想,
其實外掛所用的 manifest.json,
就是這種做法的另一種體現。
好啦。改寫完 document.onkeydown 的內容之後,
我們再改寫一下【彈出頁面】吧。
我們希望【彈出頁面】裡的快速鍵說明,
可以直接從 content.js 的 hotkeys 裡取得。
但是,【彈出頁面】與【原始內容】並不能直接溝通。
如果要進行溝通,就必須透過送禮...
哦不,是透過 sendMessage 的機制。
其實【原始內容】【彈出頁面】【背景服務】這三個遊樂場,
都是用 sendMessage 這個機制來進行溝通的。
說明解釋太多,不如直接看程式碼。
我們先看 popup.js 這邊,如何用 sendMessage 的機制,
向 content.js 提出請求:
// 發出 message,向 content-script 索取資訊
chrome.tabs.query({active: true, currentWindow: true}, function(tabs)
{
chrome.tabs.sendMessage(tabs[0].id,
{cmd: 'get_hotkeys_desc', data: {}},
(hotkeys_desc) => {
// content-script 那邊收到回應的話。。。
var ul=document.querySelector('ul#hotkey_desc')
ul.innerHTML = '' // 先清除原來的內容。。。
hotkeys_desc.forEach(item=>{
ul.innerHTML += "<li>" + item + "</li>"
})
});
});
簡單說,
我們可以用 chrome.tabs.sendMessage(...) 來送出一份 message,
其中第一個參數,
就是準備接受 message 的那個網頁的 tab id。
第二個參數,就是我們要送過去的 message,
這裡送的是一個物件,
其中的 cmd 設定了一個字串,代表我們要執行的「指令」,
另一個 data 準備用來傳送資料,不過目前不會用到。
第三個參數,是一個 callback 回調函式。
當我們的請求得到回應時,
就會把回應的東西當做參數(這裡取名為 hotkeys_desc),
丟進這個回調函式中,執行我們想要進行的動作。
以這裡為例,
就是把取得的快速鍵說明 hotkeys_desc,
逐一放入 ul 列表元素中顯示出來。
再看 content.js 這邊,是如何回應請求的:
// 回應【背景服務】或【彈出頁面】送過來的請求
chrome.runtime.onMessage.addListener( (message, sender, sendResponse) => {
if (message.cmd == 'get_hotkeys_desc') {
var hotkeys_desc = []
for (var i in hotkeys) {
hotkeys_desc.push(`【${i}】:${hotkeys[i].desc}`)
}
sendResponse(hotkeys_desc);
}
});
剛才那邊是用 sendMessage 來送出 message,
這邊則是用 onMessage 來接收 message 進行處理。
實際上,我們是利用 chrome.tabs.sendMessage.addListener,
添加了一個事件回應函式,
當它收到 message 時,也會同時收到 sender 和 sendResponse。
其中的 sender 就是這個 message 的發送者相關的訊息,這裡並沒有用到。
sendResponse 則是剛才一起送過來的 callback 回調函式。
收到這三個東西之後,事件回應函式會先執行相應的程式碼,
以這裡來說,
就是把所有快速鍵的相關說明,全都放進一個陣列(hotkeys_desc)之中。
最後再以這個 hotkeys_desc 做為回應,
把它當做參數送進 callback 回調函式去執行。
剛才已經說過,回調函式會根據 hotkeys_desc 的內容,
把快速鍵的說明呈現在彈出頁面中,
這樣一來,
就達到我們想要的效果了。
其實這個 popup.html 的能耐,
或者說 manifest 裡這個 action 的能耐,
當然不只如此。
有興趣的人,還是推薦各位可以參考之前介紹過的
《【干货】Chrome插件(扩展)开发全攻略》,
我們在這裡就先不做展開囉。
今天的內容稍多,還請各位慢慢消化,
我們明天見囉。 ^_^