Ajax This!
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:
[php]//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"]’)[/php]
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:
- localName
The name of the html tag. in the gist above, this value is customhtml.
- attribute
All attribute(s) associated with the object (if any). In the gist above, these attributes are class=”whatever” and myattribute=”customattribute”
- parentNode
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.
- textContent
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:
[php]
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;
}
[/php]
There are a few tricks here:
- I found that if the object you click on has no attributes the path doesn’t help too much, so if the object I’m building a selector for doesn’t have any attributes I just give it a class with an empty value, and it works really well.
- In an attempt to keep my selector as unique as possible I’m also using :contains() to select on specific strings as well as the path. This helps ensure that you’re manipulating the exact object you want.
- If you’re clicking on an image tag, I found that contains won’t allow you to select that image, but if we take it out it works, so I added a filter to keep image tag selectors from using :contains(). (although it currently only works in the developer tools javascript console, I haven’t yet figured out why it’s not working for my bookmarklet).
Generally, the resulting selector ends up looking something like this:
[php]
//general text version of my thisish slector
‘body[bgcolor="#FFFFFF"][text="#000000"][link="#000000"][vlink="#666666"][alink="#000000"] nobr[class=""] span[class="Body"] ul[class=""] li[class=""] font[color="000000"] b[class=""]:contains("Pra")’
[/php]
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
The Loggly and SolarWinds trademarks, service marks, and logos are the exclusive property of SolarWinds Worldwide, LLC or its affiliates. All other trademarks are the property of their respective owners.
Hoover J. Beaver