How to Achieve Visibility, Lower Costs, and Ease Troubleshooting Through Web APM Monitoring
Today’s blog post is more of a tool than a toy. Lately I’ve been working on a bookmarklet that utilizes Pusher (if you want to learn a little about Pusher, check out my previous blogpost). The bookmarklet I’m working on is really silly, but these technologies have the potential to be used for some really cool apps.
I needed to figure out a way for javascript to manipulate objects on web pages remotely through ajax calls. You can already do this with click events and Pusher by sending the class or ID of any element you click on, but what if there is no class or ID? I also want this to work on any webpage. As a result, I needed to figure out how to build a selector that was as unique as possible. One way to do this is to select using all attributes the object has to offer.
Enter, the custom attribute selector syntax:
//let's say we want all the attributes for the following html element //the selector that selects on all attributes (not just class) would look like this customhtml[class="whatever"][myattribute="customattribute"] //and you would use it with jquery like so $('customhtml[class="whatever"][myattribute="customattribute"]')
This selector will only select objects that have a class value of “whatever” AND a myattribute value of “customattribute”.
Some stack overflow and basic google searching told me I can tease attributes out using a regex(gross). I knew there had to be a better way so I started fiddling with javascript objects I captured on click and tried to figure out how to uniqify my selector string. What I discovered is that every object I clicked on contained a ton of info. The parts that I use to build my unique-ish selector are:
The name of the html tag. in the gist above, this value is customhtml.
All attribute(s) associated with the object (if any). In the gist above, these attributes are class=”whatever” and myattribute=”customattribute”
This is the object that contains the one clicked on. Technically my gist has no parent, but I use the parent node to map out a path from the object I clicked on all the way up to the body of the page. If the object you click on doesn’t have any attributes ( a
object for example) you can still select it based on the specific path to that object.
This is any text that’s in the object. For example <p>writing stuff</p> will have a textContent value of “writing stuff”.
Apart these elements aren’t bad at selecting the object you want, but together they get pretty close to behaving like a this object.
Here is how I stitch these elements together to create my this-ish selector:
function ajaxThis(ajaxyz){ var safeSelex = []; //set selectLevel equal to object var selectLevel = ajaxyz; //counter to map DOM var j = 0; //while loop traces map from object to html, and builds a //selector string that traces the path. while(selectLevel.localName != 'html') { var attrTree = ""; var tagTerm = ""; //grab html tag type to handle custom tags tagTerm = selectLevel.localName; //grabs all attributes of the object, handles custom attributes if(selectLevel.attributes.length == 0) { //if the object you click on has no attributes, we need to give it //a blank one in order for the click to register var attributeS = '[class=""]'; } else { var attributeS = ""; } for(i=0; i < selectLevel.attributes.length; i++) { attributeS += '[' + selectLevel.attributes[i].nodeName + '="' + selectLevel.attributes[i].nodeValue + '"]'; } //attribute tree puts tag and attributes together to form a selector //eg a[class="whatevz"][id="thing229"][lolzwhatever="thiswillworkforwhatever"] attrTree += tagTerm + attributeS; //build array of selectors leading to the object clicked on safeSelex[j] = attrTree; //set select level to parent of current level, to crawl up the DOM selectLevel = selectLevel.parentNode; j ++; } //grab text of attribute to select on contents as well as attributes of object var contains = ajaxyz.textContent; //build the safe selector going from body, down to the object.. //and I'm attempting to allow images to be selected. No luck so far, but the //syntax output is correct if(ajaxyz.localName == 'img') { safeSelex = "'" + safeSelex.reverse().join(' ') + "'"; } else { safeSelex = "'" + safeSelex.reverse().join(' ') + ":contains(" + '"' + contains + '"' + ")'"; } return safeSelex; }
There are a few tricks here:
Generally, the resulting selector ends up looking something like this:
//general text version of my thisish slector 'body[bgcolor="#FFFFFF"][link="#000000"][vlink="#666666"][alink="#000000"] nobr[class=""] span[class="Body"] ul[class=""] li[class=""] font[color="000000"] b[class=""]:contains("Pra")'
While this method doesn’t currently work on every single element of every single webpage, it has worked on most of the pages I have tried out, including the google search page.
Anyone who has every tried to explain, on the phone (so 1991), to a grandparent or non-tech savvy friend/family member how to do certain things on the web should be able to see the power of this. With these selectors it’s now possible to build a bookmarklet that allows two or more people to see what elements are being clicking on. Now describing webpage elements to your grandfather isn’t needed. Hook him up with your Pusher powered bookmarklet and all he has to do is go to a URL, click a bookmark button, and let’s say… click on the text you just highlighted in yellow. On the flip side, you’ll know (almost)exactly what elements on the page he’s clicking on because his clicks will make highlights on the page as well. Super neat!
Obviously this method isn’t perfected, but if you’re interested helping me make it more precise, pleasefork my project on github.
https://github.com/brainTrain/ajaxThis
Hoover J. Beaver
Share Your Thoughts