-
Notifications
You must be signed in to change notification settings - Fork 0
How to add mapping rules to GPlusX
One part of the SDK is to provide mappings from sensible human-readable keys to two sets of values:
- selectors: this will allow you to get the DOM elements for any part of the Google+ page by using querySelector or jQuery
- CSS classNames: this will allow you to modify the CSS styles of Google+
Sometimes, we already know what the selector is, e.g. #gb for the gbar. But most often, we need this library to create reliable selectors for extension development. Google+ uses class names for two different purposes: for styling and for selecting elements. This library tries to distinguish between the two and, whenever it can, it only chooses the class names that have no styling purpose and uses these as "selectors", while the rest are considered "CSS classNames".
In order for this library to automatically traverse the Google+ pages to generate new mappings, we need to write rules. These rules are based on heuristics, derived from our experience and intuition of what markers on the page are least likely to change. For instance, we know that we cannot rely on CSS class names as these are re-generated every time Google+ re-minifies its layout; and we know that some IDs and DOM structures are very stable.
Also, sometimes we have to use our judgment to decide where it makes sense to start different parts of the traversal. For example, often, it makes sense to go down the hierarchy to get at more elements. However, because we know how to get to posts very easily due to their distinctive "update-" ids and, say, we want a reference to their container, i.e. the stream, it makes sense to go up the hierarchy from the posts, rather than traverse down from the top of the page.
The G+me extension necessarily injects some wrapping divs, in particular around the content of a post and around comments. Also, the SGPlus extension injects its own type of posts into the stream. The initial version of the rules will be flexible enough to ignore these modifications. But this kind of flexibility is something to keep in mind for future extensions when writing rules; e.g. sometimes a child node will not always be what we expect and may need to start from deeper and go back up, or we may need to skip over nodes that we don't recognize.
The human-readable keys for the mappings follow the camel-case convention and must only contain characters that are valid for variable names, so that we can do something like s.gbarParent.
Often, a single element is enough to create a reliable unique selector. But sometimes, it's not enough and a selector needs even more context to get at a unique element. For example, all the parts of a post need to have the context of a post. For these elements, the key names have a _c suffix, which means that they need proper context if a unique element is sought. The SDK's API will have functions that take an optional context, much like jQuery. _cc is used to designate when such a context requires an even more specific context; e.g. some divs have the same classNames within "Old comments" and "More comments", and there'a set of Old/More comments in all posts.
There is still some tricky functionality that needs to be implemented to get a full implementation. However, we can already derive the majority of the page with the existing facilities.
This functionality is coming:
- extract selectors and classNames from other pages (right now, these rules assume the main "Stream" page which is the most interesting to us, but that will have to change)
- extract selectors and classNames from posts with different types of content, e.g. re-shared, photo albums, youtube, etc.
- extract selectors and classNames for elements that respond to user actions:
- selection of a post
- mouse hover in the gbar
- extract selectors and classNames for elements that arrive on the page a bit later:
- the "aria" menus
- blank posts that get filled in later, e.g. hangouts
- and more...
The first thing to do is to take a look at the file with the mappings rules, which will give you an idea of what they look like.
The internals of this SDK uses jQuery, although users of the SDK won't be required to use it.
There are 3 functions that are used to specify rules and extract the selectors and class names:
-
e()- extract
/**
* function(key, $el, callback)
* Extracts selector and classnames for the given key from the specified
* element.
* Only the first element will be looked at.
* @param callback: Optional callback to call with 'this' set to $el
* for convenience.
*/
-
es()- extractWithSelector
/**
* function(key, $el, selector, callback, addClassSelectors):
* Extracts selector and classnames for the given key from the specified
* element where you've already decided on a selector.
* Only processes the first element passed in.
* Example selectors: '#id' or '[role="button"]' or 'span[role="menu"]'
* @param callback: Optional callback to call with 'this' set to $el
* for convenience.
* @param addClassSelectors: If true, makes selector even more specific by adding
* classes.
*/
-
ej()- extractCallingJquery which is just a convenience method
/**
* Convenience method that calls jQuery on the selector and then
* calls es().
* Good for #id because it doesn't require a context, so this
* calls jQuery for you.
*/
ej('gbar', '#gb', function() {
e('gbarParent', $gbarParent = this.parent());
});
To get at the gbar, we know that the ID is #gb and will probably remain stable for a long time. So we've already decided on a selector -- no need to code up a rule to come up with a new selector based on class names. That's why we call one of the similar functions es (extractWithSelector) or ej (extractCallingJquery). In this case, because we don't need a context -- a simple selector is enough to get to the unique element-- then we just choose the convenience function ej, passing in the human-readable key that we want, the selector (for which jQuery will be called), and a callback function which is used for giving a starting point for the next rule.
The next rule is called within the gbar's callback so that the this variable points to the jQuery element for gbar. That's all there is to the callbacks: reuse of the element from the previous rule. You will see after writing some rules that this is a very convenient way to create rules with starting points based on other rules, in a sort of cascading fashion. The indentation also makes rules easier to understand and more robust, as they reveal dependencies between rules.
In this example, an assignment $gbarParent = is used because I want to create another dependency on that rule in a part of the rules list far away. This isn't encouraged; normally, you should be using the callback structure so that it's easy to see dependencies.
Now, you can refer back to file with the mappings rules to see what rules were written and how. If that doesn't make sense, please let Huy know, and he will clarify.
We're collaborating over at Google+ Extension Community.
Here's a sample of what the rules will generate:
Selectors:
content: "#content"
contentPane: "#contentPane"
gbar: "#gb"
gbarLinks: "#gbz"
gbarLinksList: "#gbz > .gbtc"
gbarLinksListItem: "#gbz > .gbtc > .gbt"
gbarLinksMoreUnit: "#gbztms"
gbarLinksMoreUnitText: "#gbztms1"
gbarListItem_c: ".gbt"
gbarList_c: ".gbtc"
gbarMorePullDown: "#gbd"
gbarParent: ".a-tj-S.a-c-tj-S"
gbarTools: "#gbg"
gbarToolsGear: "#gbg5"
gbarToolsGearPullDown: "#gbd5"
gbarToolsList: "#gbg > .gbtc"
gbarToolsListItem: "#gbg > .gbtc > .gbt"
gbarToolsNotificationA: "#gbg1"
gbarToolsNotificationUnit: "#gbgs1"
gbarToolsNotificationUnitBg: "#gbi1a"
gbarToolsNotificationUnitFg: "#gbi1"
gbarToolsProfileCard: "#gbmpdv"
gbarToolsProfileCardContentListItem_c: ".gbmtc"
gbarToolsProfileCardContentList_c: ".gbmcc"
gbarToolsProfileCardContent_c: ".gbpc"
gbarToolsProfileEmail_c: ".gbps2"
gbarToolsProfileName: "#gbmpn"
gbarToolsProfileNameA: "#gbg6"
gbarToolsProfileNameText: "#gbi4t"
gbarToolsProfilePhotoA: "#gbg4"
gbarToolsProfilePullDown: "#gbd4"
gbarToolsProfileSwitch: "#gbmps"
gbarToolsShareA: "#gbg3"
gbarToolsShareUnit: "#gbgs3"
gbarToolsShareUnitText: "#gbi3"
gbarTop: "#gbw"
gplusBar: ".a-C.a-aa-C"
gplusBarBg: ".a-c-aa-S"
gplusBarNav: ".l1"
gplusBarNavCirclesA: ".d-k.a-c-k-eb.m1"
gplusBarNavCirclesIcon_c: ".p1"
gplusBarNavPhotosA: ".d-k.a-c-k-eb.QA"
gplusBarNavPhotosIcon_c: ".p1"
gplusBarNavProfileA: ".d-k.a-c-k-eb.q1"
gplusBarNavProfileIcon_c: ".p1"
gplusBarNavStreamA: ".d-k.a-c-k-eb.o1"
gplusBarNavStreamIcon_c: ".p1"
post: ".tf"
postBody_c: ".Qy"
postCommentButton_c: ".Uk"
postCommentLink_c: ".wf"
postCommentsClickArea_cc: ".Dq"
postCommentsMoreClickArea_c: ".Zk > .Dq"
postCommentsMoreLeftEdge_c: ".Ln"
postCommentsMore_c: ".Zk"
postCommentsOldClickArea_c: ".al > .Dq"
postCommentsOld_c: ".al"
postCommentsShown_c: ".Gq"
postComments_c: ".Ol"
postContainer_c: ".zh"
postContent_c: ".Hq"
postHeadInfo_c: ".Qy"
postHead_c: ".Bu"
postMenuButton_c: ".Oi"
postPermissions_c: ".gl.rr"
postPlaceholder_c: ".my"
postPlusOneButton_c: "button[g\:type="plusone"]"
postShareLink_c: ".cl"
postStats_c: ".dl"
postTimeA_c: ".a-Y-k.a-c-k-eb"
postTime_c: ".fl.Br"
postTools_c: ".Jn"
postUserAvatarA_c: ".xp"
postUserAvatarImg_c: ".Rl"
postUserHeadingName_c: ".Ii"
postUserHeading_c: ".a-oa"
postUserNameA_c: ".xp"
postUserName_c: ".Bu"
postsStream: ".Wq"
searchBox: "#search-box"
searchBoxInput: "#oz-search-box"
Styling CSS classes:
content: "a-m-S"
contentPane: "a-m-K-S a-m-K-S-Rh-wb"
gbarLinksMoreUnit: "gbts gbtsa"
gbarListItem_c: "gbt"
gbarList_c: "gbtc"
gbarMorePullDown: "gbm"
gbarToolsGear: "gbgt"
gbarToolsGearPullDown: "gbm"
gbarToolsNotificationA: "gbgt"
gbarToolsNotificationUnit: "gbts"
gbarToolsNotificationUnitBg: "gbid"
gbarToolsNotificationUnitFg: "gbids"
gbarToolsProfileCardContentListItem_c: "gbmtc"
gbarToolsProfileCardContentList_c: "gbmcc"
gbarToolsProfileCardContent_c: "gbpc"
gbarToolsProfileEmail_c: "gbps2"
gbarToolsProfileName: "gbps"
gbarToolsProfileNameA: "gbgt"
gbarToolsProfilePhotoA: "gbgt gbg4a"
gbarToolsProfilePullDown: "gbm"
gbarToolsShareA: "gbgt"
gbarToolsShareUnit: "gbts"
gbarToolsShareUnitText: "gbgst"
gplusBar: "a-C a-aa-C"
gplusBarBg: "a-aa-S"
gplusBarNav: "aH d-q-p"
gplusBarNavCirclesA: "Mc d-q-p"
gplusBarNavCirclesIcon_c: "cH om"
gplusBarNavPhotosA: "Mc d-q-p"
gplusBarNavPhotosIcon_c: "cH rm"
gplusBarNavProfileA: "Mc d-q-p"
gplusBarNavProfileIcon_c: "cH sm"
gplusBarNavStreamA: "Mc d-q-p bH"
gplusBarNavStreamIcon_c: "cH qm"
post: "Ek Om"
postBody_c: "Qy"
postCommentButton_c: "Py"
postCommentLink_c: "d-k"
postCommentsClickArea_cc: "Ft"
postCommentsMoreLeftEdge_c: "Ln"
postCommentsMore_c: "My"
postCommentsOld_c: "My"
postComments_c: "iv"
postContainer_c: "ns"
postContent_c: "Gt"
postHeadInfo_c: "Qy"
postMenuButton_c: "d-k Jt"
postPermissions_c: "d-k Cp"
postPlusOneButton_c: "esw eswd ok"
postShareLink_c: "d-k"
postStats_c: "pz"
postTimeA_c: "Bp"
postTools_c: "Jn"
postUserAvatarImg_c: "Ht"
postUserHeading_c: "a-oa"
searchBoxInput: "a-Sn-E a-w-E a-Sn-TR-je"