Make Your Own Bookmarklets With jQuery

Advertisement

Bookmarklets are small JavaScript-powered applications in link form. Often “one-click” tools and functions, they’re typically used to extend the functionality of the browser and to interact with Web services. They can do things like post to your WordPress or Tumblr blog, submit any selected text to Google Search, or modify a current page’s CSS… and many other things!

Because they run on JavaScript (a client-side programming language), bookmarklets (sometimes called “favelets”) are supported by all major browsers on all platforms, without any additional plug-ins or software needed. In most instances, the user can just drag the bookmarklet link to their toolbar, and that’s it!

Make Your Own Bookmarklets with jQuery

In this article, we’ll go through how to make your own bookmarklets, using the jQuery JavaScript framework.

Getting Started

You can make a faux URI with JavaScript by prefacing the code with javascript:, like so:

<a href="javascript: alert('Arbitrary JS code!');">Alert!</a>

Notice that when we put it in the href attribute, we replaced what would normally be double quotes (“) with single quotes (‘), so the href attribute’s value and JavaScript function don’t get cut off midway. That’s not the only way to circumvent that problem, but it’ll do for now.

We can take this concept as far as we want, adding multiple lines of JavaScript inside these quote marks, with each line separated by a semicolon (;), sans line break. If your bookmarklet won’t need any updating later, this method of “all inclusiveness” will probably be fine. For this tutorial, we’ll be externalizing the JavaScript code and storing it in a .JS file, which we’ll host somewhere else.

A link to an externalized bookmarklet:

<a href="javascript:(function(){document.body.appendChild(document.createElement('script')).src='http://foo.bar/baz.js';})();">Externalized Bookmarklet</a>

This looks for the document’s body and appends a <script> element to it with a src we’ve defined, in this case, “http://foo.bar/baz.js”. Keep in mind that if the user is on an empty tab or a place which, for some reason, has no body, nothing will happen as nothing can be appended to.

You can host that .JS file wherever is convenient, but keep bandwidth in mind if you expect a ton of traffic.

Enter jQuery

Since many of you may be familiar with the jQuery framework, we’ll use that to build our bookmarklet.

The best way to get it inside of our .JS file is to append it from Google’s CDN, conditionally wrapped to only include it if necessary:

(function(){

	// the minimum version of jQuery we want
	var v = "1.3.2";

	// check prior inclusion and version
	if (window.jQuery === undefined || window.jQuery.fn.jquery < v) {
		var done = false;
		var script = document.createElement("script");
		script.src = "http://ajax.googleapis.com/ajax/libs/jquery/" + v + "/jquery.min.js";
		script.onload = script.onreadystatechange = function(){
			if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
				done = true;
				initMyBookmarklet();
			}
		};
		document.getElementsByTagName("head")[0].appendChild(script);
	} else {
		initMyBookmarklet();
	}
	
	function initMyBookmarklet() {
		(window.myBookmarklet = function() {
			// your JavaScript code goes here!
		})();
	}

})();

(Script appending from jQuery's source code, adapted by Paul Irish: http://pastie.org/462639)

That starts by defining v, the minimum version of jQuery that our code can safely use. Using that, it then checks to see if jQuery needs to be loaded. If so, it adds it to the page with cross-browser event handling support to run initMyBookmarklet when jQuery's ready. If not, it jumps straight to initMyBookmarklet, which adds the myBookmarklet to the global window object.

Grabbing Information

Depending on what kind of bookmarklet you're making, it may be worthwhile to grab information from the current page. The two most important things are document.location, which returns the page's URL, and document.title, which returns the page's title.

You can also return any text the user may have selected, but it's a little more complicated:

function getSelText() {
	var SelText = '';
	if (window.getSelection) {
		SelText = window.getSelection();
	} else if (document.getSelection) {
		SelText = document.getSelection();
	} else if (document.selection) {
		SelText = document.selection.createRange().text;
	}
	return SelText;
}

(Modified from http://www.codetoad.com/javascript_get_selected_text.asp)

Another option is to use JavaScript's input function to query the user with a pop-up:

var yourname = prompt("What's your name?","my name...");

Dealing with Characters

If you'll be putting all your JavaScript into the link itself rather than an external file, you may want a better way to nest double quotes (as in, "a quote 'within a quote'") than just demoting them into singles. Use &quot; in their place (as in, "a quote &quot;within a quote&quot;"):

<a
href="javascript:var%20yourname=prompt(&quot;What%20is%20your%20name?&quot;);alert%20(&quot;Hello,%20"+yourname+"!&quot;)">What is your name?</a>

In that example, we also encoded the spaces into %20, which may be beneficial for older browsers or to make sure the link doesn't fall apart in transit somewhere.

Within JavaScript, you may sometimes need to escape quotes. You can do so by prefacing them with a backslash ():

alert("This is a "quote" within a quote.");

Putting It All Together

Just for fun, let's make a little bookmarklet that checks to see if there's a selected word on the page, and, if there is, searches Wikipedia and shows the results in a jQuery-animated iFrame.

We'll start by combining the framework from "Enter jQuery" with the text selection function from "Grabbing Information":

(function(){

	var v = "1.3.2";

	if (window.jQuery === undefined || window.jQuery.fn.jquery < v) {
		var done = false;
		var script = document.createElement("script");
		script.src = "http://ajax.googleapis.com/ajax/libs/jquery/" + v + "/jquery.min.js";
		script.onload = script.onreadystatechange = function(){
			if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
				done = true;
				initMyBookmarklet();
			}
		};
		document.getElementsByTagName("head")[0].appendChild(script);
	} else {
		initMyBookmarklet();
	}
	
	function initMyBookmarklet() {
		(window.myBookmarklet = function() {
			function getSelText() {
				var s = '';
				if (window.getSelection) {
					s = window.getSelection();
				} else if (document.getSelection) {
					s = document.getSelection();
				} else if (document.selection) {
					s = document.selection.createRange().text;
				}
				return s;
			}
			// your JavaScript code goes here!
		})();
	}

})();

Next, we'll look for any selected text and save it to a variable, "s". If there's nothing selected, we'll try to prompt the user for something:

var s = "";
s = getSelText();
if (s == "") {
	var s = prompt("Forget something?");
}

After checking to make sure we received an actual value for "s", we'll append the new content to the document's body. In it will be: a container div ("wikiframe"), a background veil ("wikiframe_veil") and a "Loading..." paragraph, the iFrame itself, and some CSS to make things look pretty and affix everything above the actual page.

if ((s != "") && (s != null)) {
	$("body").append("
	<div id='wikiframe'>
		<div id='wikiframe_veil' style=''>
			<p>Loading...</p>
		</div>
		<iframe src='http://en.wikipedia.org/w/index.php?&search="+s+"' onload="$('#wikiframe iframe').slideDown(500);">Enable iFrames.</iframe>
		<style type='text/css'>
			#wikiframe_veil { display: none; position: fixed; width: 100%; height: 100%; top: 0; left: 0; background-color: rgba(255,255,255,.25); cursor: pointer; z-index: 900; }
			#wikiframe_veil p { color: black; font: normal normal bold 20px/20px Helvetica, sans-serif; position: absolute; top: 50%; left: 50%; width: 10em; margin: -10px auto 0 -5em; text-align: center; }
			#wikiframe iframe { display: none; position: fixed; top: 10%; left: 10%; width: 80%; height: 80%; z-index: 999; border: 10px solid rgba(0,0,0,.5); margin: -5px 0 0 -5px; }
		</style>
	</div>");
	$("#wikiframe_veil").fadeIn(750);
}

We set the iFrame's src attribute to Wikipedia's search URL plus "s". Its CSS sets it to display: none; by default, so we can have it make a grander entrance when its page is loaded via its onload attribute and a jQuery animation.

After all that's added to the page, we'll fade in the background veil.

Notice the backslashes at the end of each line of appended HTML. These allow for multiple rows and make everything easier on the eyes for editing.

Almost done, but we need to make sure these elements don't already exist before appending them. We can accomplish that by throwing the above code inside a ($("#wikiframe").length == 0) conditional statement, accompanied by some code to remove it all if the statement returns negative.

The end result .JS file:

(function(){

	var v = "1.3.2";

	if (window.jQuery === undefined || window.jQuery.fn.jquery < v) {
		var done = false;
		var script = document.createElement("script");
		script.src = "http://ajax.googleapis.com/ajax/libs/jquery/" + v + "/jquery.min.js";
		script.onload = script.onreadystatechange = function(){
			if (!done && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
				done = true;
				initMyBookmarklet();
			}
		};
		document.getElementsByTagName("head")[0].appendChild(script);
	} else {
		initMyBookmarklet();
	}
	
	function initMyBookmarklet() {
		(window.myBookmarklet = function() {
			function getSelText() {
				var s = '';
				if (window.getSelection) {
					s = window.getSelection();
				} else if (document.getSelection) {
					s = document.getSelection();
				} else if (document.selection) {
					s = document.selection.createRange().text;
				}
				return s;
			}
			if ($("#wikiframe").length == 0) {
				var s = "";
				s = getSelText();
				if (s == "") {
					var s = prompt("Forget something?");
				}
				if ((s != "") && (s != null)) {
					$("body").append("
					<div id='wikiframe'>
						<div id='wikiframe_veil' style=''>
							<p>Loading...</p>
						</div>
						<iframe src='http://en.wikipedia.org/w/index.php?&search="+s+"' onload="$('#wikiframe iframe').slideDown(500);">Enable iFrames.</iframe>
						<style type='text/css'>
							#wikiframe_veil { display: none; position: fixed; width: 100%; height: 100%; top: 0; left: 0; background-color: rgba(255,255,255,.25); cursor: pointer; z-index: 900; }
							#wikiframe_veil p { color: black; font: normal normal bold 20px/20px Helvetica, sans-serif; position: absolute; top: 50%; left: 50%; width: 10em; margin: -10px auto 0 -5em; text-align: center; }
							#wikiframe iframe { display: none; position: fixed; top: 10%; left: 10%; width: 80%; height: 80%; z-index: 999; border: 10px solid rgba(0,0,0,.5); margin: -5px 0 0 -5px; }
						</style>
					</div>");
					$("#wikiframe_veil").fadeIn(750);
				}
			} else {
				$("#wikiframe_veil").fadeOut(750);
				$("#wikiframe iframe").slideUp(500);
				setTimeout("$('#wikiframe').remove()", 750);
			}
			$("#wikiframe_veil").click(function(event){
				$("#wikiframe_veil").fadeOut(750);
				$("#wikiframe iframe").slideUp(500);
				setTimeout("$('#wikiframe').remove()", 750);
			});
		})();
	}

})();

Note that we fade out and remove the "wikiframe" content both if the user re-clicks the bookmarklet after it's loaded and if the user clicks on its background veil.

The HTML bookmarklet to load that script:

<a href="javascript:(function(){if(window.myBookmarklet!==undefined){myBookmarklet();}else{document.body.appendChild(document.createElement('script')).src='http://iamnotagoodartist.com/stuff/wikiframe2.js?';}})();">WikiFrame</a>

WikiFrame

See that (window.myBookmarklet!==undefined) conditional? That makes sure the .JS file is only appended once and jumps straight to running the myBookmarklet() function if it already exists.

Make It Better

This example was fun, but it definitely could be better.

For starters, it isn't compressed. If your script will be accessed a lot, keeping two versions of your code may be a good idea: one normal working version and one compressed minimized version. Serving the compressed one to your users will save loading time for them and bandwidth for you. Check the resource links below for some good JavaScript compressors.

While the bookmarklet technically works in IE6, its use of static positioning means that it just kind of appends itself to the bottom of the page. Not very user-friendly! With some more time and attention to rendering differences in IE, the bookmarklet could be made to function and look the same (or at least comparable) in different browsers.

In our example, we used jQuery, which is an excellent tool for developing more advanced JavaScript applications. But if your bookmarklet is simple and doesn't require a lot of CSS manipulation or animation, chances are you may not need something so advanced. Plain old JavaScript might suffice. Remember, the less you force the user to load, the faster their experience and the happier they will be.

Bookmarklets

Things to Keep in Mind and Best Practices

Untested code is broken code, as old-school programmers will tell you. While bookmarklets will run on any browser that supports JavaScript, testing them in as many browsers as you can wouldn't hurt. Especially when working with CSS, a whole slew of variables can affect the way your script works. At the very least, enlist your friends and family to test the bookmarklet on their computers and their browsers.

Speaking of CSS, remember that any content you add to a page will be affected by that page's CSS. So, applying a reset to your elements to override any potentially inherited margins, paddings or font stylings would be wise.

Because bookmarklets are, by definition, extraneous, many of the guidelines for JavaScript—such as unobtrusiveness and graceful degradation—aren't as sacred as they normally are. For the most part, though, a healthy understanding of best practices for traditional JavaScript and its frameworks will only help you:

  • Develop a coding style and stick to it. Keep it consistent, and keep it neat.
  • Take it easy on the browser. Don't run processes that you don't need, and don't create unnecessary global variables.
  • Use comments where appropriate. They make jumping back into the code later on much easier.
  • Avoid shorthand JavaScript. Use plenty of semi-colons, even when your browser would let you get away without them.

Further Resources

Helpful JavaScript Tools

JavaScript and CSS Compressors

Collections

(al)

↑ Back to top

Tommy is a web designer and coder currently living in Muncie, Indiana, USA. Someday he'll be able to grow some facial hair. His goal in life: Be Creative. Be happy.

  1. 1

    nice way to use bookmarks, thanks for sharing this

    0
  2. 2

    Definitely gonna try this for my site, thanks.

    0
  3. 3

    Ian Storm Taylor

    May 23, 2010 2:57 pm

    Incredibly useful article SM! Good work.

    0
  4. 4

    Great! Very useful to get started :) Keep up with tips and tricks like these.

    0
  5. 5

    Cool post. But one question…what if I’m running a nasty website and I suspect people are using this technique? I might redefine the jQuery object and try to do nasty things to your browser. Or perhaps I’m a nice guy, but I’ve decided to override some jQuery methods because I’ve got something way cooler in mind.

    Probably unrealistic scenarios, but I think they’re still worth consideration. Thanks!

    0
  6. 6

    Aravind Web Designer

    May 23, 2010 7:00 pm

    This is nice article

    0
  7. 7

    Simply checking for the existence of the jQuery object seems dangerous – what if the website is using jQuery 1.1, but your bookmarklet relies on 1.4.x?

    0
  8. 8

    Great to start with… Thanks :)

    0
  9. 9

    Ummmmm…

    Nice Article..

    Thanks Smashing.

    0
  10. 10

    Thank you, very good article!
    I’m going to try this one, I’ve used JavaScript and JQuery very rarely, but I think there’s plenty of uses for it right now and I can really use clear articles like this on the matter.

    0
  11. 11

    I am very glad to see such a nice article , many thanks !

    0
  12. 12

    Thank for the nice article. I learn something new which i was looking for days which is how to wait for the script to be loaded when u try to include js file with javascript.
    script.onload = script.onreadystatechange = function()
    {
    ..callback code
    }
    thanks…

    0
  13. 13

    Great summary. Like the collection of other useful Javascript links at the end.
    Thx for writing this up.

    0
  14. 14

    You should also check out a bookmarklet written with JQuery. It was a Google Chrome extension, but JQuerry made it possible to write an extension.
    It helps with the usability of Flash on websites and is almost as powerful as the extension itself.

    http://axemclion.github.com/#projects/flashresizer.html

    0
  15. 15

    You stole my post, which I submitted to you through your online service on May 20, 2010. For all readers, here is my original message to SmashingMagazine:

    “I just wrote a script that I think all of your plugin developers would
    be interested. It gets more mileage out of existing plugins by
    adapting them to become bookmarklets – which is particularly useful
    for utilities. Of course, it can be used for just writing bookmarklets
    in jQuery too.

    I hope you like it =)

    http://www.latentmotion.com/how-to-create-a-jquery-bookmarklet/

    And if you don’t believe me, just check out the post date on my site, linked above, or the first script aggregator to link to it: http://www.webappers.com

    Seriously guys, what’s the deal?

    0
  16. 16

    Thanks a bunch…can’t wait to give it a try.

    0
  17. 17

    You should NEVER rely on a currently loaded instance of jQuery for anything other than a testing site in an offline enviroment.

    As the author states in the article ‘Untested code is broken code’, using a preloaded jQuery library is a contradiction.

    0
  18. 18

    if (typeof jQuery == ‘undefined’) {

    You should use identical/triple equals === so there is no type coercion (though you probably won’t have any problems with == equality).

    jQ.onload=runthis;

    This doesn’t seem like a good practice because what if the page already has something registered in the onload event?

    0
  19. 19

    O, I googled for ie6ify bookmarklet from that picture.

    0
  20. 20

    Nice tutorial.
    I’d like to create something special, but the evil “Same Origin Policy” blocks all my attempts.
    What I want is passing objects from the user window to the iframe (or to another box) that is naturally in another webserver.
    Is there a way/hack to bypass it?

    0
  21. 21

    Bookmarklets aren’t that fun as they expected to be.

    When developing a startup for one of my customers I’ve faced an issue when bookmarklet got blocked by IE because of sending data to other domain in an iframe. Yeah, XSS. And it ruin whole potential of bookmarklets.

    0
  22. 22

    great tut buddy.. my next i am going to tell all of my friends and readers about this tut..by writing about this one @ http://rapidop.com.

    0
  23. 23

    simply AWESOME

    0
  24. 24

    I wonder if you could answer a question. I wanted to get rid of certain punctuation marks in the search terms the user submits, so I replace “+s+” in the URL string with e.g. “+s.replace(/[:?,.]/g,”)+”. The script still works when no text is selected and the user enters search terms in the alert window, and any of those punctuation marks are deleted as I’d hoped. But if text on the page is selected, when I click the bookmarklet nothing happens–the whole thing is broken. Why is this? Is there some way I could accomplish my goal?

    0
  25. 25

    awesome tut :)
    thnx for same!

    0
  26. 26

    Hi guys,

    Newbie question here … I need a bit of help with the below bookmarklet. It generates a text area which include the pagetitle, the page url and the domain name. I need to break the text into two lines between the page title and the url and, at the end of the generated text (insert 1-2 line breaks) and include the tags of the post/page where the bookmarklet is ran (I am looking at using this bookmarklet on WordPress blogs).

    Can anyone please help with adjusting the below code?

    javascript:prompt(“URL and title:”, document.title ‘ – ‘ document.location.href ‘ – ‘ ‘ via ‘ document.domain.replace(‘www.’,”));void(0)

    0
  27. 27

    Good tutorial. I’m surprised these things aren’t more popular by now than they seem to be, because for certain applications they are highly useful.
    I think the version check in the ‘Enter jQuery’ and ‘Putting It All Together’ examples is broken with the latest releases of jQuery because 1.10.X will be seen as being smaller than 1.9.x. Only a minor annoyance though, and not likely to affect many readers just yet.

    0
  28. 28

    Clicking on your bookmarklet gives the following error:

    Uncaught TypeError: Property ‘$’ of object [object Object] is not a function

    Looks like jquery might be set in no-conflict mode?

    0
  29. 29

    Bookmarklet Newbie ...

    November 9, 2013 4:33 am

    Hi guys,
    I am interested in the same things as the guy who signed Quick Question.
    Can anyone please assist?

    ————-
    Hi guys,

    Newbie question here … I need a bit of help with the below bookmarklet. It generates a text area which include the pagetitle, the page url and the domain name. I need to break the text into two lines between the page title and the url and, at the end of the generated text (insert 1-2 line breaks) and include the tags of the post/page where the bookmarklet is ran (I am looking at using this bookmarklet on WordPress blogs).

    Can anyone please help with adjusting the below code?

    javascript:prompt(“URL and title:”, document.title ‘ – ‘ document.location.href ‘ – ‘ ‘ via ‘ document.domain.replace(‘www.’,”));void(0)

    0
  30. 30

    Just wanted to say thanks for a great tutorial!

    0
  31. 31

    How i can use another function like the initMyBookmarklet where i can return some value back to the init function

    function initMyBookmarklet() {
    (window.simfy2spotify = function () {
    $(‘a.spot-track’).click(function(e){
    var url = getUrl();
    alert(url); // undefined
    });
    })()
    }
    function getUrl() {
    (window.simfy2spotify = function () {
    var url = ‘http://www…..’;
    alert(url); // http://www…..
    return url;
    })()
    }

    0
  32. 32

    I think that scenarios where an author has redefined all, or part, of the jQuery object rendering this method useless are extreme edge cases. The risks involved in using this method are minuscule, in my opinion.

    0
  33. 33

    In such a scenarios, why wouldn’t the author just write javascript independent of bookmarklets to do nasty things to a person’s computer? He wouldn’t need to depend on bookmarklets…

    1
  34. 34

    That’s a good point. Determining the version of jQuery is fairly simple – $().jQuery, which returns a version number in string form like “1.4.2″. So something like this –

    if (typeof jQuery == ‘undefined’) {
    appendJQuery();
    }
    else {
    jQVersion = $().jquery;
    versionArray = jQVersion.split(‘.’);
    if (versionArray[1] < 4) {
    appendJQuery();
    }
    else {
    runthis();
    }
    }
    function appendJQuery() {
    var jQ = document.createElement('script');
    jQ.type = 'text/javascript';
    jQ.onload=runthis;
    jQ.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js&#039;;
    document.body.appendChild(jQ);
    }
    function runthis() {
    //do whatever
    }

    0
  35. 35

    Smashing Editorial

    May 25, 2010 5:50 am

    I am sorry, Brett. But you are wrong. We didn’t steal your post, we would never do something like this. In fact, the first revision of this post was done in the end of March, and we can provide proof for this.

    0
  36. 36

    Do you think you’re the first person to come up with the idea of a bookmarklet, or something? You code shares no resemblance with the code in place here. It’s obviously not “stolen”.

    0
  37. 37

    Using JQ.onload is for the script element, not the body. It would be highly unlikely for something to register in the onload for the element you just created. So there is no interference there.

    0
  38. 38

    A quick follow….

    This won’t work in IE
    jQ.onload=runthis;

    You need this as well:
    jQ.onreadystatechange = function () {
    if (this.readyState == ‘loaded’ || this.readyState == ‘complete’)
    run_this();
    }

    Note that this.readyState triggers several times at different stages. Problem is that ‘loaded’ and ‘complete’ both indicate a finished script, but sometimes either one or both will fire. If both fire, run_this() will execute twice, so be sure to handle this condition.

    Great write up!

    0
  39. 39

    I change now my outer function into inner functions of the main init and all work.

    0

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top