DOM ISO.v.0.3.0.7.bookmarklet.js
bookmarklet for selecting and isolating an element on a page.
two sections:
section 1: Mouseover DOM, setup and handle mouse events and show information about element in informational div. Click to select, Any key to cancel.
section 2: Element Isolation with help of XPath. prompt user for XPath expression e.g., //DIV[@id='post-body']. then use XPath to select all elements not(ancestor or descendant or self), then delete those elements. also ignore self-or-descendants of head and title.
tools:
Ruderman's javascript development environment: https://www.squarefree.com/bookmarklets/webdevel.html#jsenv
Mielczarek's js to bookmarklet generator: http://ted.mielczarek.org/code/mozilla/bookmarklet.html
1
2
3 (function() {
4 //GLOBALS
5 //globals for classMausWork
6 var gSelectedElement; //currently only one selection
7 var gHoverElement; //whatever element the mouse is over
8 var gHovering=false; //mouse is over something
9 var gObjArrMW=[]; //global array of classMausWork objects. for removing event listeners when done selecting.
10
11 //extended
12 var infoDiv; //currently just container for InfoDivHover, might add more here
13 var infoDivHover; //container for hoverText text node.
14 var hoverText; //show information about current element that the mouse is over
15 //const EXPERIMENTAL_NEW_CODE=true; //debugging. new features.
16
17
18 //START
19 SetupDOMSelection();
20
21
22
23
24 //(Section 1) Element Selection
25 function SetupDOMSelection()
26 {
27
28 {
29 //setup event listeners
30 //var pathx="//div | //span | //table | //td | //tr | //ul | //ol | //li | //p";
31 var pathx="//div | //span | //table | //th | //td | //tr | //ul | //ol | //li | //p | //iframe";
32 var selection=$XPathSelect(pathx);
33 for(var element, i=0;element=selection(i);i++)
34 {
35 if(element.tagName.match(/^(div|span|table|td|tr|ul|ol|li|p)$/i)) //redundant check.
36 {
37 var m = new classMausWork(element);
38 gObjArrMW.push(m);
39 attachMouseEventListeners(m);
40 }
41 }
42 document.body.addEventListener('mousedown',MiscEvent,false);
43 document.body.addEventListener('mouseover',MiscEvent,false);
44 document.body.addEventListener('mouseout',MiscEvent,false);
45 document.addEventListener('keypress',MiscEvent,false);
46 }
47 {
48 //setup informational div to show which element the mouse is over.
49 infoDiv=document.createElement('div');
50 var s=infoDiv.style;
51 s.position='fixed';
52 s.top='0';
53 s.right='0';
54
55 s.display='block';
56 s.width='auto';
57 s.padding='0px';
58
59 document.body.appendChild(infoDiv);
60 infoDivHover=document.createElement('div');
61
62 s=infoDivHover.style;
63 s.fontWeight='bold';
64 s.padding='3px';
65 s.Opacity='0.8';
66 s.borderWidth='thin';
67 s.borderStyle='solid';
68 s.borderColor='white';
69 s.backgroundColor='black';
70 s.color='white';
71
72 infoDiv.appendChild(infoDivHover);
73 hoverText=document.createTextNode('selecting');
74 infoDivHover.appendChild(hoverText);
75 }
76 }
77
78 function CleanupDOMSelection()
79 {
80 for(var m; m=gObjArrMW.pop(); )
81 {
82 detachMouseEventListeners(m);
83 }
84 ElementRemove(infoDiv);
85 document.body.removeEventListener('mousedown',MiscEvent,false);
86 document.body.removeEventListener('mouseover',MiscEvent,false);
87 document.body.removeEventListener('mouseout',MiscEvent,false);
88 document.removeEventListener('keypress',MiscEvent,false);
89 }
90
91 function attachMouseEventListeners(c)
92 {
93 //c is object of class classMausWork
94 c.element.addEventListener("mouseover",c.mouse_over,false);
95 c.element.addEventListener("mouseout",c.mouse_out,false);
96 c.element.addEventListener("mousedown",c.mouse_click,false);
97 }
98
99 function detachMouseEventListeners(c)
100 {
101 //c is object of class classMausWork
102 c.resetElementStyle();
103 c.element.removeEventListener("mouseover",c.mouse_over,false);
104 c.element.removeEventListener("mouseout",c.mouse_out,false);
105 c.element.removeEventListener("mousedown",c.mouse_click,false);
106 }
107
108 //mouse event handling class for element, el.
109 function classMausWork(element)
110 {
111 //store information about the element this object is assigned to handle. element, original style, etc.
112 this.element=element;
113
114 var elementStyle=element.getAttribute('style');
115 var target;
116
117 this.mouse_over=function(ev)
118 {
119 if(gHovering)return;
120 var e=element;
121 var s=e.style;
122 s.backgroundColor='yellow';
123 s.borderWidth='thin';
124 s.borderColor='lime';
125 s.borderStyle='solid';
126 InfoMSG(ElementInfo(e),'yellow','blue','yellow');
127 gHoverElement=e;
128 gHovering=true;
129 target=ev.target;
130 ev.stopPropagation();
131 };
132
133 this.mouse_out=function(ev)
134 {
135 if(!gHovering)return;
136 if(gHoverElement!=element ||ev.target!=target)return;
137 var e=element;
138 e.setAttribute('style',elementStyle);
139 InfoMSG('-','white','black','white');
140 gHoverElement=null;
141 gHovering=false;
142 target=null;
143 //ev.stopPropagation();
144 };
145
146 this.mouse_click=function(ev)
147 {
148 if(!gHovering)return;
149 if(gHoverElement!=element ||ev.target!=target)return;
150 var e=element;
151 e.setAttribute('style',elementStyle);
152 ev.stopPropagation();
153 CleanupDOMSelection();
154 gHoverElement=null;
155 gHovering=false;
156 target=null;
157
158 if(ev.button==0)
159 {
160 gSelectedElement=e;
161 ElementSelected(e); //finished selecting, cleanup then move to next part (section 2), element isolation.
162 }
163 };
164
165 this.resetElementStyle=function()
166 {
167 element.setAttribute('style',elementStyle);
168 };
169 }
170
171 function MiscEvent(ev) //keypress, and mouseover/mouseout/mousedown event on body. cancel selecting.
172 {
173 if(ev.type=='mouseout' && !gHovering)
174 {
175 InfoMSG('-','white','black','white');
176 }
177 else if(ev.type=='mouseover' && !gHovering)
178 {
179 InfoMSG('cancel','yellow','red','yellow');
180 }
181 else //keypress on document or mousedown on body, cancel ops.
182 {
183 CleanupDOMSelection();
184 }
185 }
186
187 function InfoMSG(text,color,bgcolor,border)
188 {
189
190 var s=infoDivHover.style;
191 if(color)s.color=color;
192 if(bgcolor)s.backgroundColor=bgcolor;
193 if(border)s.borderColor=border;
194 if(text)hoverText.data=text;
195 }
196
197
198
199
200
201 //(Section 2) Element Isolation
202 function ElementSelected(element) //finished selecting element. setup string to prompt user.
203 {
204 PromptUserXpath(ElementInfo(element));
205 }
206
207
208 function PromptUserXpath(defaultpath) //prompt user, isolate element.
209 {
210 var userpath = prompt("XPath of elements to isolate : ", defaultpath);
211 if(userpath && userpath.length>0)
212 {
213 var addPredicate = "[count(./ancestor-or-self::head)=0][count(./ancestor-or-self::title)=0]"; //exclude head & title elements from selection so they aren't removed
214 var addPath = "//script | //form | //object | //embed"; //include these elements in selection for removal
215 var pathx=TransformXPath_NoAncestorDescendentSelf(userpath, addPredicate, addPath); //the xpath selection of all elements to be removed/deleted.
216
217 try
218 {
219 var element;
220 var elements=$XPathSelect(pathx);
221 for(var i=0;element=elements(i);i++)
222 {
223
224 if(!element.nodeName.match(/^(head|title)$/i)) //redundant check.
225 {
226 ElementRemove(element);
227 }
228 }
229 }
230
231 catch(err)
232 {
233 alert("wtf: "+err);
234 }
235
236 }
237 }
238
239
240
241 //support
242 function $XPathSelect(p, context)
243 {
244 if (!context) context = document;
245 var i, arr = [], xpr = document.evaluate(p, context, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
246 return function(x) { return xpr.snapshotItem(x); }; //closure. wooot! returns function-type array of elements (usually elements, or something else depending on the xpath expression).
247 }
248
249 function ElementRemove(e)
250 {
251 if(e)e.parentNode.removeChild(e);
252 }
253
254 function ElementInfo(element)
255 {
256 var txt='';
257 if(element)
258 {
259 txt=element.tagName.toLowerCase(); //txt=element.tagName;
260 txt=attrib(txt,element,'id');
261 txt=attrib(txt,element,'class');
262 txt='//'+txt;
263 }
264 return txt;
265
266 function attrib(t,e,a)
267 {
268 if(e.hasAttribute(a))
269 {
270 t+="[@"+a+"='"+e.getAttribute(a)+"']";
271 }
272 return t;
273 }
274
275 }
276
277
278
279 //function to 'invert' the XPath by selecting all elements that are not ancestor and not descendent and not self.
280 function TransformXPath_NoAncestorDescendentSelf(u, includePredicates, includePaths)
281 {
282
283 //sample input (u): //div[@class='sortbox']
284 //sample output //*[ not(./descendant-or-self::*=//div[@class='sortbox'])][ not(./ancestor-or-self::*=//div[@class='sortbox'])]
285 //sample output with additional conditions: //*[ not(./descendant-or-self::*=//div[@class='sortbox'])][ not(./ancestor-or-self::*=//div[@class='sortbox'])][count(./ancestor-or-self::head)=0][count(./ancestor-or-self::title)=0]
286
287 //obsolete method. much faster but can only be used for limited types of (simple) xpath expressions -- unlike the current version, which should be able to convert any xpath.
288 //input: table[@id='topbar']
289 //output: //*[not(./descendant-or-self::table[@id='topbar']) and not(./ancestor-or-self::table[@id='topbar'])]
290 //output (alternative): //*[count(./descendant-or-self::table[@id='topbar'])=0 and count(./ancestor-or-self::table[@id='topbar'])=0]
291
292
293 var o1= './descendant-or-self::*='+gr(u);
294 o1= 'not' + gr(o1);
295 o1= nt(o1);
296 var o2= './ancestor-or-self::*='+gr(u);
297 o2= 'not' + gr(o2);
298 o2= nt(o2);
299
300 var o= '//*'+o1+o2;
301 if(includePredicates && includePredicates.length>0) o += includePredicates;
302 if(includePaths && includePaths.length>0) o += ' | ' + includePaths;
303 return o;
304
305
306 function nt(term){return wrap(term,'[]');} //node test; predicate - enclose with bracket.
307 function gr(term){return wrap(term,'()');} //group - parenthesize.
308 function wrap(term, enclosure){return enclosure.charAt(0)+term+enclosure.charAt(1);}
309 }
310
311
312
313
314 })();
315