前陣子在微博看到關於chrome書籤搜索的擴展程序Holmes,因而使用了一下,確實還不錯,尤爲書籤多不少的狀況下,有個搜索功能 ,比定時作書籤分類等等方便得多不少。看着擴展程序功能簡單,感受實現應該不難,因而我就試着查看一下擴展程序源碼,也能夠順便進一步瞭解chrome擴展程序的實現方法和操做api。javascript
本篇文章意在講解Holmes的主要實現,對於chrome擴展程序怎麼一步一步創建起來,就不作重複說明,網上搜索便可。css
首先,既然要研究擴展程序源碼,那麼最直接的方式固然就是從chrome安裝目錄來尋址該擴展程序的所在目錄了。html
這點,其實網上有資料能夠查看的,好比這裏>>java
步驟1,在chrome地址欄輸入:chrome://version
回車,將結果頁中「我的資料路徑」一欄對應的值複製(實際上是一個目錄路徑)jquery
步驟2,打開資源管理器,鍵入剛複製的路徑,進入目錄,打開Extsions
目錄,裏面都是各類以字母組合的命名文件夾,對應的實際上是各個擴展程序,命名實際上是擴展程序的idgit
步驟3,在地址欄輸入:chrome://extensions
,進入擴展程序管理頁,找到對應的擴展程序選項,有ID
,對應的ID值就是對應的擴展程序文件夾名字。這彷佛是個惟一值,holmes的ID是:gokficnebmomagijbakglkcmhdbchbhn
github
遵循上面三個步驟,咱們就能夠找到Holmes的擴展程序目錄了 ,將其複製到方便查看的位置,就能夠開始查看源碼了。web
作過擴展程序的都知道,項目必須有一個配置文件manifest.json
chrome
這裏列一下主要的配置字段,不全編程
{ "background": { "page": "watson.html" }, "browser_action": { "default_icon": "images/icon_19.png", "default_popup": "holmes.html", "default_title": "Holmes" }, "commands": { "_execute_browser_action": { "suggested_key": { "default": "Alt+Shift+H" } } }, "description": "Chrome Bookmark Search Extension", "icons": { "128": "images/icon_128.png", "16": "images/icon_19.png", "48": "images/icon_48.png" }, "manifest_version": 2, "minimum_chrome_version": "25", "name": "Holmes", "omnibox": { "keyword": "*" }, "permissions": [ "bookmarks", "tabs", "chrome://favicon/" ], "update_url": "https://clients2.google.com/service/update2/crx", "version": "3.2.1" }
能夠看到,這裏有個permissions字段,字面意義應該是跟權限相關,這裏配置了bookmarks、tabs、和chrome://favicon/
,這爲後面要用到chrome的一些功能埋下了伏筆(這麼說好麼?= =)
另外browse_action
字段定義了點擊擴展程序圖標的一些行爲,default_popup
規定了點擊彈出框定位到holmes.html
,也就是擴展程序的業務頁面,即搜索框和搜索結果。
找到holmes.html,查看頁面會發現,頁面主要引入的js文件:
jquery 庫文件 fuse.min.js 模糊查詢工具 jquery.Core.js 基於jquery的核心方法 keymaster.min.js 按鍵事件工具 jquery.holmes.js 基於jquery的頁面業務邏輯
頁面的主要dom結構:
<div id="link-wrap" class="cf"> <a class="icon-question" href="http://www.blackfish.fi/holmes/" target="_blank" title="Holmes website"></a> </div> <section> <header class="cf"> <div id="search"> <img src="images/icon_19.png"> <input id="pattern" type="text" autofocus="autofocus" autocomplete="off" placeholder="Holmes at your service."> </div> </header> </section> <section> <div id="view"> <ol></ol> </div> </section>
主要的節點是:輸入框 #pattern
和 搜索結果 #view
.
到此,咱們大概瞭解了holmes主要用到的是jquery來方便編程,利用keymaster來處理按鍵事件,利用fuse的模糊搜索來實現模糊查詢書籤。
那麼,咱們怎麼得到chrome的書籤呢?若是書籤有增刪改,又該怎麼辦呢?另外,我要怎麼打開標籤頁來承載某個書籤呢?
獲取書籤,其實就呼應了前面說過的配置權限permissions
.
咱們怎麼知道的? 首先定位到jquery.Core.js
文件,文件內容很簡單,不過作了壓縮,這裏我優化了一下方便走讀。
(function() { var o, r; window.Holmes = { bookmarks: [], version: 3.21, showShortcutInfo: !1 }; r = function(o) { return function(o) { var e, t, n, a; for (a = [], t = 0, n = o.length; n > t; t++) { e = o[t]; if(e.hasOwnProperty("children")){ a.push(r(e.children)); }else{ if(e.url.match(/^javascript/)){ a.push(void 0); }else{ (e.title || (e.title = e.url), a.push(Holmes.bookmarks.push({ title: e.title, url: e.url }))); } } } return a } }(this); o = function() { return chrome.bookmarks.getTree(function(o) { return Holmes.bookmarks = [], r(o) }) }; chrome.bookmarks.onCreated.addListener(function(r, e) { return o() }); chrome.bookmarks.onRemoved.addListener(function(r, e) { return o() }); chrome.bookmarks.onChanged.addListener(function(r, e) { return o() }); Holmes.getPlaceholder = function() { var o; return o = ["Holmes at your service.", "How can I help you?", "It is a good day for searching.", "You know my methods. Apply them."], o[Math.floor(Math.random() * o.length)] }; o() }).call(this);
這裏主要是定義了全局對象:Holmes
,包含三個屬性,其中最主要的是bookmarks
,它是一個數組,存儲chrome上的書籤,怎麼存的呢?
在上述代碼上能夠看出,經過調用chrome.bookmarks.getTree
, chrome.bookmarks
是chrome提供的書籤對象api,用於各類書籤操做,chrome書籤操做>>, 這裏getTree就是獲取書籤樹結構,參數爲回調函數,從回調函數參數能夠得到樹節點,經過children能夠得到子節點,因而這裏是實現對節點的處理 ,當有children節點時,遞歸操做,無則當作連接獲取title跟url屬性,並push到bookmarks數組去(有可能收藏的是腳本代碼,這塊這裏作了過濾處理)。
要用chrome.bookmarks
,從官方文檔可知道, 須要配置permissions權限
{ "name": "My extension", ... "permissions": [ "bookmarks" ], ... }
另外,bookmarks對象還有事件處理函數,這裏用於更新書籤數據以備檢索。
chrome.bookmarks.onCreated.addListener chrome.bookmarks.onChangeed.addListener chrome.bookmarks.onRemoved.addListener
這裏還沒涉及到tabs,但由於都要配置permissions
,因此提早說明,一樣,tabs也有官網文檔說明
其配置權限相似bookmarks,關於tabs的使用至關簡單:
chrome.tabs.create({url:xxx, pinned:true}) // 打開標籤頁,url爲連接, pinned是否固定標籤頁
標籤頁這裏主要是打開標籤頁,那就只須要用到這條api,其餘能夠到上面文檔說明去查看。
業務邏輯在holmes.js內,主要涉及的是按鍵事件交互處理以及檢索結果展現。
對於檢索結果,實際上是從全局對象Holmes.bookmarks
入手, 對其採用fuse模糊搜索。
fuse的具體操做能夠看 fuse官網
使用很簡單,以下:
// 配置對象,檢索關鍵屬性 var option = { keys:['id'] }; // data爲測試數據 f = new Fuse(data, options); f.search('查找內容'); //這裏將對data的id屬性作 查找內容 的搜索
在holmes裏,使用很簡單:在輸入框監聽keyup,清空結果dom($view
), 對書籤數據進行模糊查詢(鍵值:書籤標題),獲得結果調用updateView方法來給$view
更新內容。(涉及到鍵入監聽,其實還能夠考慮作節流操做避免重複執行搜索)
代碼以下:
// 監聽輸入框keyup事件 $('#pattern').on('keyup', function(e) { var _kc, _ret, f, options; _kc = e.keyCode; if (_kc === 38 || _kc === 40 || _kc === 37 || _kc === 39) { $(this).css('-webkit-user-select', 'text'); return false; } pattern = $('#pattern').val(); // 清空搜索結果 $view.html(''); _ret = []; if (pattern.length > 0) { pattern.replace(' ', ''); options = { keys: ['title'] }; // 對title進行查詢 f = new Fuse(Holmes.bookmarks, options); _ret = f.search(pattern); } else { _ret = []; search = null; } // 調用updateView對搜索結果進行展現 return updateView(_ret); });
關於updateView
: 已經設定了bmarks_per_page
變量用來定義結果顯示數量(並不是把所有書籤結果都輸出,而是輸出前十條數據),而且對標題進行省略裁剪,並針對關鍵字插入span
標籤(可作樣式高亮,但擴展程序並無作),在拼接dom模板時對首條數據加入current類,而且將對應的dom保存到$current_mark
,用於回車時直接打開以及顯示選中項,對拼接的dom添加到$view
中,而且定義a標籤點擊事件(因爲每次搜索結果都是從新添加內容,因此這裏不會涉及重複綁定,但其實能夠給$view
作事件代理更佳)
代碼以下:
matchString = function(_string, _char_count) { var _inp_val_pat; _string = _string.length < _char_count ? _string : _string.substr(0, _char_count) + '...'; _inp_val_pat = new RegExp(pattern, "gi"); return _string.replace(_inp_val_pat, '<span>' + _inp_val_pat.exec(_string) + '</span>'); }; updateView = function(bmarks) { var _classes, _match, _update, i, j, len, mark; if (bmarks.length > 0) { _update = ''; for (i = j = 0, len = bmarks.length; j < len; i = ++j) { mark = bmarks[i]; // 我認爲這裏直接break跳出循環便可,無需continue繼續循環 if (!(i < bmarks_per_page)) { continue; } _classes = i === 0 ? 'current' : ''; _match = matchString(mark.title, 50); _update += "<li class=\"" + _classes + " cf\"><img src=\"chrome://favicon/" + mark.url + "\"><a href=\"" + mark.url + "\" title=\"" + mark.url + "\">" + _match + "</a></li>"; } $view.html(_update); $current_mark = $view.children(':first'); return $view.find('a').on('click', function() { return chrome.tabs.create({ url: this.href }); }); } else { if (search != null) { $view.html('<div class="no-bookmarks-found"><h1>Oh no!</h1><p><b>No single bookmark found.</b><br>Press enter to Google!</p><p>Or you could check out<br><a href="http://www.blackfish.fi/holmes/" target="_blank" title="Holmes website">Holmes new website!</a></p></div>'); } return $current_mark = null; } };
ps:這裏有個小知識點,其實也涉及到permissions
配置,在chrome下,chrome://favicon/
+頁面地址 能夠得到該網站的favicon.
其餘按鍵交互:除了輸入框鍵入字符的監聽外,還監聽了回車打開標籤頁和切換結果選中項,主要用了keymaster
其api能夠參考這裏:keymaster官網
主要的代碼:
key.filter = function(event) { var tagName; tagName = (event.target || event.srcElement).tagName; return !(tagName === 'SELECT' || tagName === 'TEXTAREA'); }; key('down, up', function(e, h) { if (($current_mark != null) && $view.children().length > 1) { switch (h.shortcut) { case 'down': if ($current_mark.index() !== bmarks_per_page - 1 && $current_mark.index() < $view.children().length - 1) { return $current_mark = $current_mark.removeClass('current') .next('li') .addClass('current'); } break; case 'up': if ($current_mark.index() !== 0) { return $current_mark = $current_mark.removeClass('current') .prev('li') .addClass('current'); } } } }); key('enter, shift + enter', function(e, h) { switch (h.shortcut) { case 'enter': case 'shift+enter': if ($current_mark != null) { return chrome.tabs.create({ url: $current_mark.find('a').prop('href'), pinned: key.shift ? true : false }); } else { if (pattern.length > 2) { return chrome.tabs.create({ url: 'http://www.google.com/search?q=' + pattern }); } } } });
至此,holmes的主要邏輯也就解讀完畢了, 對於樣式那塊,本文不作解讀,可看出,其實該擴展程序,還有不少優化空間,感興趣,其實能夠本身改改,來優化使用體驗,好比:代碼的一些重複邏輯、加入事件代理、搜索結果顯示體驗等等。(目前,這個擴展程序在說明上貌似最新一版已是2015年的事了,看來做者也沒有繼續更新的打算了)