Create site specific adapter

In this example we create an adapter for Google Search and a dapplet for it. The application will insert buttons to Google search results and a badge to the profile icon.

Here is the initial code for this example: ex08-new-adapter-exercise.

The adapter and the dapplet are divided into two directories: /adapter and /dapplet-feature.

Create the adapter#

I. Adapter config#

At the beginning we change the adapter template. There is a config in adapter/index.json. This is JSON with selectors that are needed to find and parse various contexts on the page. There are selectors for context data, and widget insertion points. contextBuilder defines a context information (called ctx in our examples) that widgets receive in this context types – GLOBAL, POST and PROFILE in our case.

1{
2 "themes": {
3 "LIGHT": "boolean(//body[@style='background-color: #FFFFFF;'] | //body[@style='background-color: rgb(255, 255, 255);'])",
4 "DARK": "boolean(//body[@style='background-color: rgb(21, 32, 43);'] | //body[@style='background-color: rgb(0, 0, 0);'])"
5 },
6 "contexts": {
7 "GLOBAL": {
8 "containerSelector": "html",
9 "contextBuilder": {
10 "id": "string('global')",
11 "websiteName": "string(//title)"
12 }
13 },
14 "POST": {
15 "containerSelector": "#center_col",
16 "contextSelector": ".MjjYud > .g",
17 "widgets": {
18 "button": {
19 "styles": "",
20 "insertionPoint": "div[data-snf=x5WNvb]",
21 "insert": "end"
22 }
23 },
24 "contextBuilder": {
25 "id": "string(.//*[(@jsname='UWckNb')]/@href)",
26 "title": "string(.//*[(@jsname='UWckNb')]//h3)",
27 "link": "string(.//*[(@jsname='UWckNb')]/@href)"
28 }
29 },
30 "PROFILE": {
31 "containerSelector": "#gb",
32 "contextSelector": ".gb_d.gb_Da.gb_H",
33 "widgets": {
34 "avatarBadge": {
35 "styles": "",
36 "insertionPoint": "",
37 "insert": "end"
38 }
39 },
40 "contextBuilder": {
41 "id": "normalize-space(string(@aria-label))"
42 }
43 }
44 }
45}
note

Now we are talking about site-specific adapters. It means that dapplets using this adapter interact with some specific website. It also means that we should use the website's HTML structure to add our widgets to certain parts of the pages.

The idea of separating adapters from dapplets is to provide dapplets' developers with a simple interface to add their augmentationss to existing pages. This way, dapplets' developers don't need to worry about how to add their code in certain places or how to parse different blocks of information on the page. They get the template, customize it and add the behavior they need.

The goals of the adapters' developer are to create this template, define the data that can be parsed from the context, that can be useful in dapplets. Adapters' developer also need to describe exact places on the pages where the widgets will be inserted.

For example, let's look at the Google Search page. Enter some search query. Look at the structure of the page in the browser's developer tools, Elements tab. There you can find the block with center_col id, that contains all the main search results. It will be our containerSelector where we will search some separate results.

document.querySelectorAll('#center_col')
NodeList [div#center_col.s6JM6d]

Then try to pick out the selectors' chain that provides access to separate context — contextSelector. You can choose relevant selectors manually or you can left click on the element in the Elements tab and choose Copy selector. In most cases the selector has to be edited.

document.querySelectorAll('#center_col .MjjYud > .g')
NodeList(10) [div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc, div.g.Ww4FFb.vt6azd.tF2Cxc.asEBEc]

Make sure not to include unwanted blocks.

Using the same method define selectors for insertion points — exact places where the widgets will be placed. There are 3 insert options: end, begin and inside. The first one is default.

"insertionPoint": "div[data-snf=x5WNvb]",
"insert": "end"

!! Note that websites can be changed and you will get errors trying to get properties when the nodes will not be found.

To parse useful information from the context, we must specify queries in XPath format for the contextBuilder configuration.

1"contextBuilder": {
2 "id": "string(.//*[(@jsname='UWckNb')]/@href)",
3 "title": "string(.//*[(@jsname='UWckNb')]//h3)",
4 "link": "string(.//*[(@jsname='UWckNb')]/@href)"
5}

It is assumed that all interactions with DOM happen in the adapters and not in the dapplets.

II. Widgets#

The next step is creating styles for widgets. We have an example of the button's style in styles/post/button.css and an example of the avatar badge's style in styles/profile/avatarBadge.css. You can change them if you want.

Paths for the styles should be specified in the config:

"button": {
"styles": "styles/post/button.css",
}
"avatarBadge": {
"styles": "styles/profile/avatarBadge.css",
}

Widgets are currently defined in the extension. The adapter developer can style widgets for his adapter using CSS at his discretion. This can be the text of the button, the image on the icon, the choice of one of the location options, etc. The adapter developer decides what parameters to make customizable. They should be described in the documentation as follows: parameter's name, mandatory or not, data TYPE, text description. If you need to select one of several value options for a parameter, they must be listed (this can be specified in the parameter type). If the parameter type is a number, then it is recommended to indicate in which units it will be converted: pixels, percentages, fractions, etc.

Currently only button and avatarBadge widgets are available. The list of widgets is planned to be expanded in the near future.

Create the dapplet#

  1. Insert the correct adapter name
@Inject('example-google-adapter.dapplet-base.eth')
public adapter
  1. Log the POST context information to the console and show an alert with the title of the search result item on the button click
1exec: () => {
2 console.log('ctx:', ctx)
3 Core.alert('Title: ' + ctx.title)
4},
  1. Add an avatar badge to the profile icon and log the PROFILE context information to the console
1PROFILE: (ctx) =>
2 avatarBadge({
3 DEFAULT: {
4 vertical: 'bottom',
5 horizontal: 'right',
6 img: EXAMPLE_IMG,
7 exec: () => console.log('ctx:', ctx),
8 },
9 }),

Here is the result: ex08-new-adapter-solution.

Run the dapplet:

npm i
npm start

video