-
Notifications
You must be signed in to change notification settings - Fork 37
Accessing the main page from a Plugin
Sometimes, a plugin will want to reach outside of its iframe. It will want to:
- get state
- set state
- listen for state changes
To do this, use window.parent.postMessage().
Here's a plugin that documents how to set Overview's selection from within a plugin:
https://github.com/overview/overview-apps/blob/master/document-list-params/show.html
Here's what you need to know:
- This interface is made for plugins to control the Overview web client on behalf of the user.
-
window.parent.postMessage()
happens from the plugin web page. If you want your plugin's HTTP backend to control Overview's web client, you'll need to do it through your plugin web page. -
window.parent.postMessage(...)
returns immediately, and it never gives a return value. - Your plugin web page can only
postMessage
to the Overview web client it appears in: it cannot message any other recipient. In particular, the Overview web client is not the Overview REST API, nor is it Overview's web server. (Your plugin may access Overview's REST API over traditional HTTP; it may not send requests to Overview's web server outside of the API.)
Think of a postMessage()
call to Overview as a function call. Here's an example:
const message = {
call: 'refineDocumentListParams', // call
args: [ { q: 'search' } ], // arguments
}
window.parent.postMessage(message, '*') // postMessage() with message and origin
- The call is one of the messages documented below.
- The args is an Array of arguments. Most calls accept one argument, so use a single-element Array.
-
window.parent.postMessage()
requires an "origin" argument, and we passed'*'
here. That's a red flag, so let's go into detail. The origin security layer lets your plugin prevent itself from sending a message to a server you don't expect. That keeps your plugin's data away from an evil Overview lookalike. If your plugin derives its data from a validapiKey
(which an evil Overview lookalike can't guess), and if your plugin is accessible from multiple Overview instances (such as a development instance, a staging instance and a production instance), then it makes sense to pass'*'
as the origin. Many plugins don't store any data at all: for them, an origin of'*'
is fine.
The Overview web page will keep your plugin abreast of what the user is doing. Listen for its messages like this:
window.addEventListener('message', function(ev) {
// TODO make sure ev.origin is what we expect. This is only important if your
// plugin opens an iframe itself -- in that case, this message may come from its
// parent or its child iframe, and we want to adjust our actions accordingly
console.log(`Event: ${JSON.stringify(ev.data.event)}`) // e.g. "notify:documentListParams"
console.log(`Arguments: ${JSON.stringify(ev.data.args)}`) // e.g. [ { q: "search" } ]
// (Now do something with the message, if you care about it.)
})
Overview will post these messages to the plugin iframe, each containing event
and args
, whether or not you have added a listener.
As mentioned above, there is no return value from window.parent.postMessage()
. But you can request that Overview send a notify:
message. This is useful when your plugin wants to access information during page load: first your plugin needs to start listening for messages, and then it can request that Overview notify it of what it cares about.
For instance:
window.addEventListener('message', function(ev) {
if (ev.data.event === 'notify:documentListParams') {
console.log("The document list is now: " + JSON.stringify(ev.data.args[0]))
}
})
window.parent.postMessage({ call: 'notifyDocumentListParams' }, '*')
// Very soon, Overview will send a 'notify:documentListParams' message.
The Overview web client will automatically send these messages to your plugin after their values change or your plugin requests them.
-
notify:documentListParams
:args[0]
contains the DocumentListParams, an Object describing the current search criteria for the documents the Overview web client is displaying. In this guide, we've used{ q: "search" }
as a frequent example. See https://github.com/overview/overview-server/blob/master/web/js/apps/Show/models/DocumentListParams.js for living documentation. -
notify:documentList
:args[0]
contains an Object with{ length: <Number> }
. The document list length changes tonull
whenever the user tries to search for new documents; it will change to non-null
when the server responds with a count. -
notify:documentSet
:args[0]
contains a DocumentSet Object. This has ametadataSchema
Object describing all metadata fields. -
notify:document
:args[0]
contains an Object with{ id, title, snippet, pageNumber, url, metadata, indexInDocumentList, isFromOcr, pdfNotes }
.
Your plugin may send these messages to the Overview web client at any time. Each will make the Overview web client do something:
-
Calls that modify Overview's state
-
goToNextDocument()
: navigate to the next document in the document list. -
goToPreviousDocument()
: navigate to the previous document in the document list. -
notifyDocumentSet()
: send the plugin anotify:documentSet
message. -
notifyDocument()
: send the plugin anotify:document
message. -
notifyDocumentList()
: send the plugin anotify:documentList
message. -
notifyDocumentListParams()
: send the plugin anotify:documentListParams
message. -
refineDocumentListParams(changedParams)
: changes the given aspect(s) of the search parameters and searches for documents matching the new parameters. For instance, if the user has already searched for tag "A" and you send{ q: "search" }
, then the user will now be searching for documents matching the search phrase "search" with tag "A". You can also "un-set" a certain part of the search by passing it asnull
: for instance,{ q: null }
. -
setDocumentListParams(newParams)
: changes all aspects of the search parameters and searches for documents matching what you request. For instance, if the user has already searched for tag "A" and you send{ q: "search" }
, then the user will now be searching for documents matching the search phrase "search" with any tag.
-
-
Calls that let Overview invoke your plugin
-
postMessageToPluginIframes(...)
: sends awindow.postMessage()
message to all plugin iframes. This calling convention is different: call it like{ call: 'postMessageToPluginIframes', message: "here is the message" }
. The recipients will seemessage
as theirev.data
. In this example, they will see"here is the message"
: Overview will not touch the message, and it won't wrap it with anevent
orargs
. -
setDocumentDetailLink({ url, title, text, iconClass })
: shows a "details" prompt above each document from the{ url, title, text, iconClass }
object you pass. When the user clicks the link, a popup will appear withurl
in an iframe (replacing:documentId
in the URL with the current document ID). Overview will addapiToken
,documentSetId
andserver
to the URL's query string. If two Views set the sameurl
, only the first View's link will appear. -
setModalDialog(options)
: opens a modal dialog atop of Overview containing aniframe
pointed atoptions.url
. Pass{ url: null }
to dismiss the dialog. (The user can also dismiss the dialog by clicking an "X" in its corner.) -
setRightPane(options)
: opens a right pane to the right of the document list containing aniframe
pointed atoptions.url
. Pass{ url: null }
to close the pane. -
setViewFilter(options)
: adds another search filter that works even when the plugin is not visible. This requires several options:-
renderOptions
: see FilterView comments -
choices
: see FilterView comments -
url
: a URL -- presumably to an endpoint in your plugin -- that the Overview server will access when searching for documents. Overview will send an HTTPGET
request to the given URL, addingapiToken
,ids
andoperation
to the query string.- Security risk: since Overview sends an API key to this URL, it grants the URL's web server access to all your documents. Use an HTTPS URL that you trust with your documents.
- The
ids
are Strings the plugin specified inoptions.choices
, comma-separated. (Do not allow commas in your IDs.) - The
operation
determines how the filter is to interpret those IDs: documents matching"any"
,"all"
or"none"
of them. - The server must respond with an
application/octet-stream
bitset. - The bitset must map
documentId & 0xffffffff
to1
iff the document should appear in the returned document list. - The bitset must be encoded such that the most-significant bit of the first byte will be document
0
, the second-most-significant bit will be document1
, and so on. Overview will ignore bits referring to documents that do not exist. - Overview will AND this bitset with all other filters': the search phrase, tags, and any other ViewFilters.
-
-
setViewFilterChoices(choices)
: overwritessetViewFilter
'soptions.choices
. Does not change the current document list. -
setViewFilterSelection({ ids, operation })
: setsids
andoperation
in the select box that you created withsetViewFilter
.ids
is an Array of Strings;operation
is one of"any"
,"all"
or"none"
.
-
-
Calls that interact with the PDF view, the graphical view Overview displays for most documents:
-
beginCreatePdfNote()
: If a PDF is being displayed, clicks the "Add Note" button in it. When a note is added, your plugin will receive anotify:document
message. -
goToPdfNote(pdfNote)
: If a PDF is being displayed, call this with one of thedocument.pdfNotes
values from the previousnotify:document
message to scroll the PDF to that note for viewing or editing.
-
-
Calls that relate to document metadata:
-
patchDocument(options)
: if you passoptions.title
, saves a new title on the document. If you passoptions.metadata
, saves new metadata. (These will cause anotify:document
.) -
openMetadataSchemaEditor()
: open the metadata schema editor.
-