Context-menu API 提供了簡單、宣告式的方法來增加物件到網頁的 context menu 中。我們可以增加當被點擊時做出反應的物件、子選單( submenu )、和分隔符號( separator )。
我們結合物件( item )和某個情境( context ),然後新增和移除該物件的的動作就會自動處理,而不需要在某情境發生或結束時,手動新增和移除物件。物件和情境結合,就類似事件和其監聽函式綁定一樣。當使用者喚起某個 context menu,所有與其綁定的物件就自動備增加到其中。如果沒有物件被綁定,則 context menu 終究部會增加物件。相同地,任何原本在 menu 中,但沒有被綁定到當前情境的物件,會被自動從 menu 中移除。我們完全不需要手動移除 menu 中的物件,除非我們希望那些物件不要再出現。
舉例來說,如果我們的 add-on 需要在使用者每次拜訪某個頁面的時候,新增一個 context menu 物件,那麼當該頁面載入時不要建立這個物件、頁面停止載入時也不要移除這個物件。而是應該只建立物件一次,然後提供符合目標 URL 的情境。
指定情境
就如同字面上的意思,context menu 應該要為某特定情境的發生而保留。所謂情境,可以是與網頁的內容相關、或者是和網頁本身相關,但絕不會是超過網頁相關的範圍。
舉個例子,正確使用 menu 的用法是,當使用者右鍵點擊網頁中的圖片時,menu 中會顯示 "編輯圖片" 物件;不正確的用法會是,顯示列出所有使用者分頁的子選單,因為分頁既跟網頁無關也和使用者點擊的東西無關。
網頁情境( page context )
首先,我們可能根本不需要指定一個情境。當一個物件沒有指定情境時,等於套用網頁情境。
當使用者從網頁中非互動( non-interactive )的部分喚起 context menu 時,網頁情境就發生了。試試看右鍵點擊本頁中空白的地方、或者是文字(但不要反白到字)。這時候顯示的 menu 應該會包含"上一頁"、"下一頁"、"重新載入"、"停止"等等物件。這就是網頁情境( page context )。
當選單物件將網頁是為一個整體來反映時,用網頁情境才是適當的。當使用者從連結、圖片、或其他非文字的網頁節點( node )叫出 context menu 時,不會出現網頁情境。
宣告情境( Declarative Contexts )
我們可以藉由設定 context 屬性給 context menu 的建構子,來指定一些簡單、宣告式的情境:
var cm = require("context-menu"); cm.Item({ label: "My Menu Item", context: cm.URLContext("*.mozilla.org") });這些情境可能藉由呼叫以下的建構子以被指定給 context menu。每個建構以都是由 context menu 模組產生:
Menu 物件也有一個在建構之後可以增加和移除宣告情境( declarative contexts )的
context
屬性,如:var context = require("context-menu").SelectorContext("img"); myMenuItem.context.add(context); myMenuItem.context.remove(context);當一個選單物件和一個以上的情境綁定時,這個物件會在所有這些情境發生時出現。
In Content Scripts
宣告式情境很方便使用但功能不強大。舉例來說,我們可能希望選單物件在任何網頁中有圖片時出現,但宣告式情境做不到。
當我們想要更深入操控選單物件出現的情境時,我們可以使用 content script。和 SDK 中其他 API 相同,context-menu API 使用 content script 來讓 add-on 與瀏覽器中的網頁互動。每個我們建立在第一層context menu 中的選單物件都可以有一個 content script。
當context menu 要被顯示出來時,有一個叫做 "
context
" 的特殊事件會在 context script 中被發佈(emit)出來。如果我們註冊監聽這個事件的函式且回傳 true,則與這個監聽函式的 content script 關聯的選單物件就會顯示在 menu 中。例如,這個物件會在 context menu 從有圖片的網頁中被喚起時出現:
require("context-menu").Item({ label: "This Page Has Images", contentScript: 'self.on("context", function (node) {' + ' return !!document.querySelector("img");' + //weired double escalations '});' });這裡要注意的是,監聽函式有一個參數
node
。這個 node 代表使用者右鍵點擊到喚起 menu 的節點。我們可以用這個參數來決定選單物件該不該顯示。我們可以既指定宣告式情境、也監聽某 content script 中的情境。在這種情況下,宣告式情境會被優先處理。如果當前情境不符合我們設定的宣告式情境,則 content script 中的監聽函式不會被執行。
以下的例子就是利用這種特性。監聽函式可以被保證
node
一定是圖片:require("context-menu").Item({ label: "A Mozilla Image", context: contextMenu.SelectorContext("img"), contentScript: 'self.on("context", function (node) {' + ' return /mozilla/.test(node.src);' + '});' });上例中,我們的選單物件只會在同時滿足當前情境符合所有宣告式情境,且 context 監聽函式回傳真的時候顯示。
Handling Menu Item Clicks
除了如上述使用 content script 來監聽 "
context
" 事件,我們也可以使用 content script 來處理選單物件被點擊的事件。當使用者點擊選單物件,一個叫做 "click
" 的事件會在此物件的 content script 中被發佈出來。因此,若要處理物件被點擊的情況,則在該物件的 content script 中監聽 "
click
" 事件,如下:require("context-menu").Item({ label: "My Item", contentScript: 'self.on("click", function (node, data) {' + ' console.log("Item clicked!");' + '});' });程式碼中第 4 行的 Console 用法請見這裡。
注意,監聽函是有兩個參數
node
和 data
。node
使用者點擊叫出 context menu 的節點,我們可以用其來執行某些動作。data
是被點擊的選單物件的 data
屬性。因為只有最上層的選單物件(Item 建構子)有 content script,所以我們用 data
來判斷哪個 Menu
中的物件被點擊:var cm = require("context-menu"); cm.Menu({ label: "My Menu", contentScript: 'self.on("click", function (node, data) {' + ' console.log("You clicked " + data);' + '});', items: [ cm.Item({ label: "Item 1", data: "item1" }), cm.Item({ label: "Item 2", data: "item2" }), cm.Item({ label: "Item 3", data: "item3" }) ] });上例中我們使用的是
Menu
建構子而不是 Item
建構子。兩者的差異是,Menu
產生 context menu 中一個可以點擊的選單物件;而 Item
產生的是有子選單的選單物件。通常我們會需要從 click 事件的監聽函式蒐集某些資訊來執行與網頁內容無關的動作。為了和選單物件及其 content script 溝通,content script 可以呼叫全域
self
物件的 postMessage
方法,來傳遞JSON-able data。選單物件的 "message
" 事件監聽函式接收到這個 data 時會被呼叫。require("context-menu").Item({ label: "Edit Image", context: contextMenu.SelectorContext("img"), contentScript: 'self.on("click", function (node, data) {' + ' self.postMessage(node.src);' + '});', onMessage: function (imgSrc) { openImageEditor(imgSrc); } });
Updating a Menu Item's Label
每個選單物件在被建立時都必須指定 label,不過我們可以在之後使用一些方法來改變它。
最簡單的方法就是設定選單物件的
label
屬性。以下的例子根據物件被點擊的次數來改變 label 的內容:var numClicks = 0; var myItem = require("context-menu").Item({ label: "Click Me: " + numClicks, contentScript: 'self.on("click", self.postMessage);', onMessage: function () { numClicks++; this.label = "Click Me: " + numClicks; // Setting myItem.label is equivalent. } });
有時候我們可能想要根據情境來改變 label 的內容。舉例來說,如果我們的物件會對使用者所選取的詞句進行搜尋,那麼把要搜尋的詞句顯示在選單物件中,以提供回饋給使用者,會是個不錯的做法。在這種情況下,我們可以使用第二個方法。回想一下,我們的 content script 可以監聽 "
context
" 事件,若回傳值為真,選單物件與其 content script 就會顯示在選單中。除了回傳值為真之外,我們的 "context
" 監聽函式也可以回傳字串。當 "context
" 監聽函式回傳字串時,其會變成物件的新 label。以下實作前述的搜尋範例:
var cm = require("context-menu"); cm.Item({ label: "Search Google", context: cm.SelectionContext(), contentScript: 'self.on("context", function () {' + ' var text = window.getSelection().toString();' + ' if (text.length 20)' + ' text = text.substr(0, 20) + "...";' + ' return "Search Google for " + text;' + '});' });"
context
" 監聽函式取得視窗內當前所選取的內容,如果太長則將其截短,並把這個內容包含到回傳的字串中。當選單物件顯示出來時,其 label 會顯示 "Search Google for text",其中 text 就是所選取的(可能被截短過的)內容。了解更多 Context-menu 類別的詳細建構子、方法、屬性、事件,請參考這裡。
沒有留言:
張貼留言