BookMarklets - et fint værktøj

JavaScript protokollen

Da Netscape udviklede JavaScript (herefter kaldet: JS), var der nok ikke mange, der havde forestillet sig, hvor kompleks webscripting hurtigt ville udvikle sig til at blive. Selvom der allerede ved introduktionen var tænkt på event-handlers (onclick, onmouseover, osv) på HTML-elementer, forestillede man sig åbenbart, at JS ofte ville blive afviklet ved klik på links. I hvert fald udvikledes JS-protokollen, som f.eks. kan bruges til at kalde et JS i href'en på et link.
I stedet for at kalde en webside's URL gennem HTTP- eller FTP-protokollen, som i:

<a href="http://www.google.com">LINK</a>

- kan man kalde et JS på følgende måde:

<a href="javascript:alert('Jamen, det virker jo!')">LINK</a>

En anden måde at bruge protokollen på, er i adresselinjen på en browser.
Prøv f.eks. at skrive dette i adresselinjen i din browser:

javascript:alert("Sådan virker det da osse!")

- og tryk 'Return'.

*Hot dooooooog* ;o)

X-Domain scripting?

De fleste, der har prøvet at JS'e, kender sikkert sikkerhedsreglen, der forskriver, at det ikke er tilladt at scripte fra et dokument under ét domæne ind i et dokument under et andet domæne. Den regel, der gør, man ikke kan scripte ind i Google's sider, hvis man importerer dem i et frameset.
Nu kunne man umiddelbart tro, at denne regel også ville forhindre scripting på en webside gennem adresselinjen. Det er ikke tilfældet ... som vi skal se om et øjeblik, kan vi sagtens gøre Google rød.

Hva' dæ'len er nu det?

Den snu læser har måske allerede noget à la dette i tankerne:

javascript:document.body.style.backgroundColor="red"

Prøv det - og du vil se, browseren overskriver dokumentet med ordet 'red' - *hrmmmpf* ... hvorfor nu det? :o|

Det sker, fordi udtrykket 'document.body.style.backgroundColor="red"' returnerer 'red' - og browseren skriver, hvad der måtte blive returneret. Et kald til alert() returnerer derimod intet - hvorfor dokumentet ikke overskrives, når vi prøver det.
Prøv f.eks. at skrive dette i stedet:

javascript:alert(document.body.style.backgroundColor="red")

En bedre måde at udgå overskrivningen af dokumentet er brugen af void-operatoren. Afsluttes scriptet med void(0), returneres intet fra scriptet.
Prøv nu:

javascript:document.body.style.backgroundColor="red";void(0)

- mucho bedre, señor! ;o)

JavaScript som BookMarklets

Se, hvis nu man kunne gemme sådan et script som et bookmark, ville man hurtigt kunne scripte ind i enhver webside. Det kan man heldigvis - og det kaldes en BookMarklet ;o)

Prøv at skrive dette link i et HTML-dokument:

<a href="javascript:document.body.style.backgroundColor='red';void(0)">BM-Test</a>

(- husk at holde styr på dine gåseøjne, når du skriver scriptet ind i href'en. Det hører under alm. script-viden og er ikke noget, jeg vil gå dybere ind i her - men man skal ved mere komplekse scripts holde tungen lige i munden).

Kald nu dokumentet i en browser, højreklik på linket og vælg 'Føj til Foretrukne...'. Der vil nu poppe en advarselsboks op, som advarer om, at du er ved at gemme et script som en Foretrukken - og at det kan være en sikkerhedsrisiko. Du ved godt, hvad du gør - så du accepterer ;o)

Du kan nu gå til et hvilket somhelst websted og farve baggrunden rød ved at vælge din Favorit med navnet 'BM-Test' ... de par gange, det er sjovt =)

Er du for doven til selv at skrive dokumentet, er det færdige link her: BM-Test

BookMarklets som værktøj

Som sagt bruger jeg selv BookMarklets som værktøj til at inspicere websider med. I begyndelse brugte jeg ofte en popup til at vise informationer i, men det er unægtelig blevet mere besværligt. Hvis ikke ens popup-blokker, skal stoppe popup'en, må man udløse den via et klik på et element.
Man kan også indskrive et div - og så indskrive informationerne i det - eller man kan nøjes med at alert'e dem. Det er en afvejning af bedste virkemåde op mod antal tegn i det aktuelle tilfælde.

Når jeg bruger en popup, vælger jeg somregel at indskive en knap i dokumentet - men sommetider vælger jeg bare at lægge en window.open på body'ens onclick-event. Der er nemlig en vigtig ting at tage hensyn til:

Hele strengen (incl. 'javascript:') må være max. 495 tegn lang!

BookMarlet-kodning er derfor et studie i komprimering! Hvert tegn betyder noget - og til tider må man acceptere en ikke helt korrekt syntaks for at få hele skidtet med. Her er stedet, hvor 'bare det virker' gælder - men også kun her ;o)

En kort tutorial

En opgave kunne bestå i at lave en BookMarklet, der i en boks på siden viser hvilke meta-keywords, en webside indeholder. Udviklings dokumentet til en sådan BookMarklet kan f.eks. se sådan ud:

<html>
<head>
    <meta name="keywords" content="ordA ordB ordC ordD">
<script type="text/JavaScript">
function testBM() {
    var d, a, c, aT, s;
    d = document; // Vi forkorter bare lidt allerede

// Læg start-tag'et til et div i strengen
    s = "<div style=\"position:absolute;z-index:1000;left:10px;top:10px;width:300px;padding:10px;background-color:yellow\">";
    a = d.getElementsByTagName("meta"); // Find alle metas
    for (i=0; i<a.length; i++) { // Kør dem igennem
        if (a[i].name.toLowerCase()=="keywords") { // Hvis dette element er det rigtige
            c = (a[i].content.indexOf(",")!=-1)? "," : " "; // Find delimiter tegnet, der er brugt
            aT = a[i].content.split(c); // Opret et array af keywords
            s += aT.join("<br>"); // Sæt det sammen igen med line-breaks imellem - og føj til strengen
        }
    }
    s += "</div>"; // Afslut div
    
// alert("Debug:\n\257\257\257\257\257\257\n" + s);
    
    d.body.innerHTML += s;
}
</script>
</head>
<body>

<button onclick="testBM()">TEST</button>

</body>
</html>

Læg mærke til, at jeg allerede bruger korte variabelnavne, men på dette tidspunkt af processen er det selvfølgelig en smagssag. Jeg har kommenteret rigeligt, så det skulle være muligt at følge med.Det skal dog lige nævnes, at det udover at escape gåseøjne af og til kan være nødvendigt at bruge oktal-værdier. Her svarer \42 til et dobbelt gåseøje - og \47 til et enkelt (en apostrof).
Hvis du ikke kender til brugen af oktal-værdier, kan du prøve denne alert:

alert("\42Dette er en test mellem dobbelte gåseøjne - og dette \47 er en apostrof.\42");

Når skidtet virker, kan vi gå skridtet videre og skrive scriptet sammen. Først springer vi variablen over, der indeholder adskillelses tegnet, der er brugt mellem keywords:

function testBM() {
    var d, a, aT, s;
    d = document; // Vi forkorter bare lidt allerede

// Læg start-tag'et til et div i strengen
    s = "<div style=\"position:absolute;z-index:1000;left:10px;top:10px;width:300px;padding:10px;background-color:yellow\">";
    a = d.getElementsByTagName("meta"); // Find alle metas
    for (i=0; i<a.length; i++) { // Kør dem igennem
        if (a[i].name.toLowerCase()=="keywords") { // Hvis dette element er det rigtige
            aT = a[i].content.split((a[i].content.indexOf(",")!=-1)? "," : " "); // Opret et array af keywords
            s += aT.join("<br>"); // Sæt det sammen igen med line-breaks imellem - og føj til strengen
        }
    }
    s += "</div>"; // Afslut div
    
// alert("Debug:\n\257\257\257\257\257\257\n" + s);
    
    d.body.innerHTML += s;
}

Derefter skærer vi den endnu en tak ned ved at join'e array'et af keywords i samme hug, som vi opretter det:

function testBM() {
    var d, a, s;
    d = document; // Vi forkorter bare lidt allerede
    
// Læg start-tag'et til et div i strengen
    s = "<div style=\"position:absolute;z-index:1000;left:10px;top:10px;width:300px;padding:10px;background-color:yellow\">";
    a = d.getElementsByTagName("meta"); // Find alle metas
    for (i=0; i<a.length; i++) { // Kør dem igennem
        if (a[i].name.toLowerCase()=="keywords") { // Hvis dette element er det rigtige
            s += a[i].content.split((a[i].content.indexOf(",")!=-1)? "," : " ").join("<br>");
        }
    }
    s += "</div>"; // Afslut div
    
// alert("Debug:\n\257\257\257\257\257\257\n" + s);
    
    d.body.innerHTML += s;
} 

Komprimeringen skal foregå gradvist, så man hele tiden kan debugge koden, hvis den ikke virker. Målet er at udrydde alle overflødige tegn ('#ff0' fylder f.eks. et par tegn mindre end 'yellow'). Sidste trin, inden vi kyler det hele på én linie kunne se sådan ud (debug-alert'en er det sidste, der ryger ud):

function testBM() {
    var d,a,s="<div style=\"position:absolute;z-index:1000;left:10px;top:10px;width:300px;padding:10px;background-color:#ff0\">";
    d=document;
    a=d.getElementsByTagName("meta");
    for(i=0;i<a.length;i++){if(a[i].name.toLowerCase()=="keywords"){s+=a[i].content.split(a[i].content.indexOf(",")!=-1?",":" ").join("<br>")}};
    s+="</div>";
    
// alert("Debug:\n\257\257\257\257\257\257\n" + s);
    
    d.body.innerHTML+=s;
}

Til allersidst samler du det hele på én lang linje, sætter 'javascript:' foran og 'void(0)' efter. Nu kan du - hvis du bruger en editor, der kan det - sætte cursoren efter det sidste bogstav og aflæse, hvormange tegn du har brugt. I dette tilfælde er der ingen ko på isen, da vi kun bruger 362 tegn, men var vi kommet over 495, måtte vi til at luge ud.

Under hele processen er det vigtigt at holde øje med, at hvert JS-udtryk er adskilt af et semikolon. Det kan godt betale sig at holde god kodestil og afslutte alle linjer med semikolon ;o)
Overholder man den regel, ender man dog op med nogle få overflødige, der bør slettes. Bemærk: Det er aldrig nødvendigt med et semikolon før en afslutnings-tuborg '}'.

Den endelige kode til BookMarklet-link'et bliver med lidt gåseøjegymnastik:

- koden er foldet og bør foldes ud til én linje før brug -

<a href="javascript:var d,a,s='<div style=\'position:absolute;z-index:1000;left:10px;top:10px;width:300px;
padding:10px;background-color:yellow\'>';d=document;a=d.getElementsByTagName('meta');for(i=0;i<a.length;i++){
if(a[i].name.toLowerCase()=='keywords'){s+=a[i].content.split(a[i].content.indexOf(',')!=-1?',':' ').join('<br>')}};
s+='</div>';d.body.innerHTML+=s;void(0)">OBBM - [GetKeyWords]</a>

Også her følger lidt bruger-venlighed for den dovne: OBBM - [GetKeyWords] ;o)

Et par færdige BookMarklets

Til slut vil jeg gerne takke Eksperten-brugeren foxmulder58 for inspirationen til denne artikel. Da jeg så hans i øvrigt udmærkede, lille artikel: http://www.eksperten.dk/guide/763, tænkte jeg, at det var da en oplagt idé til en BookMarklet. Som sagt, så gjort:

- koden er foldet og bør foldes ud til én linje før brug -

<a href="javascript:d=document;b=d.body;b.innerHTML+='<p id=\'z\' style=\'position:absolute;background:#ff0;
padding:5px;z-index:10000\'>&nbsp;</p>';d.onmousemove=function(e){q=d.getElementById('z');e=e?e:event;
se=e.srcElement?e.srcElement:e.target;tn=se.tagName;c=q.style;s='<b>'+se.tagName+'</b><br>id: '+se.id+'<br>
class: '+se.className;q.innerHTML=s;E=d.documentElement;m=Math.max;L='scrollLeft';T='scrollTop';c.left=e.clientX+m(E[L],b[L])+5+'px';
c.top=e.clientY+m(E[T],b[T])+5+'px'};void(0)">OBBM - [InspectPage]</a>

- og som færdigt link: OBBM - [InspectPage]

Bevares, 'inderfjæset' er nok ikke så fancy - men det virker rimeligt - er altid ved hånden ... og så er det X-browser =)


Denne BookMarklet indskriver en knap på siden. Et klik på den åbner en popup med links til alle dokumenter, inkluderet med LINK- og SCRIPT-elementer:

- koden er foldet og bør foldes ud til én linje før brug -

<a href="javascript:d=document;x='<button style=\42position:absolute;z-index:999;left:9px;top:9px\42 onclick=\42w=window.open(\'\',\'\');
z=d.getElementsByTagName;a=z(\'link\');b=z(\'script\');s=\'\';for(i=0;i<a.length;i++){if(a[i].href){
s+=\'<a href=\'+a[i].href+\'>\'+a[i].href+\'</a><br>\'}};s+=\'<hr>\';for(i=0;i<b.length;i++){
s+=\'<a href=\'+b[i].src+\'>\'+b[i].src+\'</a><br>\'};with(w.document){write(s);close()}\42 style=\42position:absolute;
top:10px\42>Open</button>';d.body.innerHTML+=x;void(0)">OBBM - GetIncludes</a>

- og som færdigt link: OBBM - [GetIncludes]


Her en BookMarklet til brug på YouTube. YouTube gemmer langt de fleste videoer i mp4 format 'bag scenen' - og disse film giver denne BookMarklet mulighed for at downloade:

- koden er foldet og bør foldes ud til én linje før brug -

<a href="javascript:d=document;gE=d.getElementById;if(gE('download-youtube-video')==null&&!!(location.href.match(/http:\/\/[a-zA-Z\.]*youtube\.com\/watch/))){
p='http://www.youtube.com/get_video?fmt=18&video_id='+swfArgs['video_id']+'&t='+swfArgs['t'];o=gE('watch-embed-div');
if(o){oo=d.createElement('div');oo.innerHTML='<br><span id=\'download-youtube-video\'><a href=\''+p+'\'>Download as MP4</a> '
+((navigator.userAgent.indexOf('Safari')!=-1)?'(Control-click and select <i>Download linked file as</i>)':('(Right-click and select <i>Save '
+(navigator.appName=='Microsoft Internet Explorer'?'target':'link') +' as)</i>'))+'</span>';o.appendChild(oo)}}void(0);">OBBM - [DownMP4]</a>

- og som færdigt link: OBBM - [DownMP4]

Når du står på en YouTube side, kan du fyre BookMarklet'en af, hvilket indskriver et download link i den grå boks til højre for videoen - lige under embed koden.


Til slut en BookMarklet, som validerer siden, den bliver fyret af på:

- koden er foldet og bør foldes ud til én linje før brug -

<a href="javascript:if(location.protocol.indexOf('http')!=0){alert('Du kan kun validere online filer.')}
else location.href='http://validator.w3.org/check?uri='+encodeURIComponent(location.href)
+'&charset=%28detect+automatically%29&doctype=Inline&group=0&ss=1&verbose=1'">OBBM - [Validate]</a>

- og som færdigt link: OBBM - [Validate]

Når du står på en side og vælger denne BookMarklet, sendes du til W3C's validator og får vist resultatet for siden.

God fornøjelse

Jeg håber, artiklen har været til inspiration - der er rigtig mange anvendelsesområder, når man først 'slår cellen til' =)

Jeg håber også, at en og anden server-scripter, der endnu ikke har fået øjnene op, indser at sikkerhed hører hjemme på serveren - og kun der. Alt, hvad der foregår i et alm. HTML-dokument, kan der pilles ved ... også formular felter og validerings scripts =8-O

/mvh
Ole Clausen