It so happened that for several months I was developing bookmarklets, making about a dozen of them. Used both jQuery and native JavaScript. About what pitfalls I encountered, what I learned and found new - this will be discussed under the cut.
1. Connection
Those who have already engaged in the development of bookmarklets, this item can be skipped, for the rest - a simple example.
< a href ="javascript: (function(){var a=document.createElement('script');a.type='text/javascript';a.src='http://example.com/bookm.js?'+(new Date).getTime()/1E5;document.getElementsByTagName('head')[0].appendChild(a)})();" onclick ="alert('Drag this button to your browser\'s bookmarks toolbar or right-click it and choose Bookmark This Link.');return false;" title ="Bookmarklet Title" > < img src ="http://example.com/img/bookmarklet.gif" align ="absmiddle" alt ="Bookmarklet Title" border ="0" />
</ a >
"Href" in a more readable form:
javascript: ( function () {
var a = document .createElement( 'script' );
a.type = 'text/javascript' ;
a.src = 'http://example.com/bookm.js?' + ( new Date).getTime() / 1E5;
document .getElementsByTagName( 'head' )[0].appendChild(a)
})();
Here we create the <script> </ script> element, use our bookmarklet as src, and add it to the page in the <head> </ head>. Record
')
( new Date).getTime() / 1E5
Allows you to always connect the current version of the bookmarklet, avoiding its caching. If you are not going to change its content often, you can remove it.
Such a bookmarklet can be dragged by Drag-and-Drop to toolbars in Chrome, Safari and FireFox. For IE, you will need to select the option to add to the bookmarks panel in the context menu.
2. Structure
So, consider the contents of our bookm.js. In general, there are two styles of writing bookmarklets:
2.1 Functional approach
( function () {
try {
//
} catch (e) {
console.log(e.name + ": " + e.message);
}
}())
The main advantage of this approach is that the global scope of variables remains absolutely clean; we do not clog it with our own variables. But this approach only works for small, simple scripts.
2.2 Object-oriented approach
function ourSuperBookmarklet() {
// , this.a = '10';
}
ourSuperBookmarklet.prototype.someOtherFunction = function (abc) {
this .a += abc;
}
ourSuperBookmarklet.prototype.start = function () {
//
this .someOtherFunction(abc);
}
try {
var __osb = new ourSuperBookmarklet();
__osb.start();
} catch (e) {
console.log(e.name + ": " + e.message);
}
It turned out a little more.
So, here we describe the class, its properties and methods. Then in the try-catch construction we create an instance of this class and run the loader method.
In this case ourSuperBookmarklet and __osb fall into the global scope, but we really need it (more on that later).
We must never forget that our code is injected directly into the site on which the user went, and our errors can break the entire page. try-catch will help us a little. The main thing - do not forget to remove all console.log'and from the production version.
3. Working with a class instance
Consider this example.
ourSuperBookmarklet.prototype.start = function () {
var si = setInterval( function () {
if (something) {
clearInterval(si);
this .a++;
}
}, 500);
}
this.a - in this case, we want to refer to the property of our class. In this record, nothing will come out, because this will be a pointer to the function of the interval. You can do this:
ourSuperBookmarklet.prototype.start = function () {
var th = this ;
var si = setInterval( function () {
if (something) {
clearInterval(si);
th.a++;
}
}, 500);
}
And it will work. However, creating a special variable in each method, so that it is the same everywhere - more like a crutch.
A more elegant solution (IMHO!) Is to refer to a class instance, like this:
ourSuperBookmarklet.prototype.start = function () {
var si = setInterval( function () {
if (something) {
clearInterval(si);
__osb.a++;
}
}, 500);
}
And this is a huge plus of an object-oriented approach. Perhaps this example is not very convincing, but look here:
ourSuperBookmarklet.prototype.someOtherFunction = function (text, obj) {
obj.innerHTML = text;
}
ourSuperBookmarklet.prototype.start = function () {
var a = document .createElement( 'div' );
a.innerHTML = 'qwerty' ;
a.setAttribute( 'ommouseover' , '__osb.someOtherFunction(\'asdf\', this);' )
document .body.appendChild(a);
}
Thus, we can access the methods and properties of our class from other scripts / events, etc. What is good.
4. AJAX
Here I hurry to upset you. Your favorite $ .post and $ .get will not work here.
The fact is that at the browser level, the xmlHttpRequest component is prohibited from accessing another domain (this does not apply to all browsers, and it will work somewhere, if you want, check), more details
en.wikipedia.org/wiki/Same_origin_policy . And since our code becomes part of the site on which the user is logged in, then accessing our site is an appeal to another domain.
However, not a single xmlHttpRequest. At one time, I myself came to the method described below, but then I found that this method is quite popular.
ourSuperBookmarklet.prototype.request = function (str) {
var script = document .createElement( 'script' );
script.setAttribute( 'type' , 'text/javascript' );
script.setAttribute( 'src' , str);
document .getElementsByTagName( 'head' )[0].appendChild(script);
}
It's simple, and very similar to how we connected the bookmarklet (see the beginning). Just if we just need to transfer the data. And if we want to get a callback? Here I use 2 methods, and they are both associated with JSON.
4.1 JSONP
Take an example from wikipedia. We have an object
{ "Name" : "Cheeso" , "Rank" : 7}
And we pass it using a script (in our case, it is created dynamically).
< script type ="text/javascript" src ="http://server2.example.com/getjson?jsonp=parseResponse" ></ script >
As a result, we need to pass
parseResponse({ "Name" : "Cheeso" , "Rank" : 7})
Where parseResponse is a callback function in the bookmarklet that will process the transferred object. In our case it would be something like
__osb.parseResponse({ "Name" : "Cheeso" , "Rank" : 7})
4.2 JSONP, but without P
Everything is good, but how to find out if we could not request the right answer? For example, our site lay down. In this case, I use the construct:
var c = 0;
var ci = setInterval( function () {
if (__osb.getInfo) {
clearInterval(ci);
//
}
if (c > __osb.timeout) {
clearInterval(ci);
//
}
c++;
}, 1000);
Here __osb.timeout is the number of seconds during which we will wait for a response from the server. The question is what is __osb.getInfo?
Initially (in the class constructor) we initialized getInfo as null. And the response from the server will be:
__osb.getInfo = { "Name" : "Cheeso" , "Rank" : 7}
Therefore, as soon as __osb.getInfo becomes non-null, we will finish waiting for it and begin processing the response, otherwise we will display an error message.
5. JSON
Since we are talking about JSON, I would like to move a little away from the topic and report the good news. In the latest versions of popular browsers, a native window.JSON object has appeared that can encode / decode objects / strings.
JSON.stringify is used to encode an object into a string, and JSON.parse for inverse transformation. A native JSON object performs these transformations faster than our hand-written solutions. Therefore, I recommend checking for the presence of a window.JSON object, and only if it is not available, use your own conversion solutions.
Instead of conclusion
In the examples, I used only native JavaScript, however you can connect your favorite framework before turning on the bookmarklet itself. The reasons why I usually do not use frameworks:
- Execution speed If we have a lot of objects that we often run through, then the issues of processor time and memory come first. Pure JavaScript will always work faster than frameworks. Although they are more convenient.
- Reliability. Using native functions for working with DOM I guarantee that the script will work the way I want. You can argue that I have to write a lot of extra code to maintain cross-browser compatibility. Wake up, people! For all the time I wrote bookmarklets, I remember one major difference between IE and other browsers (working with selected text) and a couple of small ones.
- Independence from CDNs. All you need is a user's browser for work If you do not use CDN, then this is a serious argument; if you use it, I’ll be a little paranoid and say that theoretically they can also be dropped / broken and so on ...
- The desire to learn native javascript and DOM. For me it was an important reason ;-)