Using jsonp in bookmarklets
Wednesday, September 1, 2010 4:44:08 PM
Использование jsonp в букмарклетах/кнопках
Полезные ссылки
http://javascript.ru/unsorted/bookmarklet - Хорошее введение в предмет.dean.edwards.name/packer/ - Пакует скрипты, убирая комментарии, пробелы, переносы строк, может укорачивать имена переменных.
Javascript unpacker and beautifier - Выполняет противоположную задачу, "распаковывая" сжатый код.
Javascript Buttonator - Создаёт кнопки для Opera, в том числе и из букмарклетов.
Но сначала о получении выделенного текста
Задача может показаться элементарной, да в сущности она и была таковой, пока в opera 10.5 не удалили объект document.selection. И теперь получить весь выделенный текст, включая input-ы и textarea, стало достаточно проблематично.Соответствующее стандартам решение предполагает наличие на странице обработчика onFocus и в букмарклете малоприменимо.
Альтернативой могло бы быть использование document.activeElement, но в opera при вызове букмарклета фокус слетает на [object HTMLBodyElement] (хотя в остальных браузерах всё нормально), так что и этот вариант не подходит. Тоже самое относится и к хаку с "textarea:focus" отсюда.
Ну и последний оставшийся вариант, это перебор всех input-ов и textarea и поиск в них выделенного текста. Способ не идеальный, т.к. реальное выделение может оказаться и в другом input-е, но другой альтернативы похоже нет. Приведу код для этого варианта, с поддержкой фреймов:
var getSel = function (w) {
var s, d = w.document;
if (d.selection) {
var r = d.selection.createRange();
s = r ? r.text : ''
} else {
s = d.getSelection();
if (!s) for (var i = 0, t = d.querySelectorAll('textarea,input'), e; e = t[ i ]; i++) {
if (s = e.value.substring(e.selectionStart, e.selectionEnd)) break
}
};
if (!s) for (var j = 0, f; f = w.frames[j]; j++) {
try {
if (s = arguments.callee(f)) break
} catch(e) {}
};
return s
};
var str = getSel(window);
Кроссбраузерный вариант:
var getSel = function (w) {
var s, d = w.document;
// ie-шная модель удобнее, но поддерживается только в opera < 10.5
if (d.selection) {
var r = d.selection.createRange();
// необходимая проверка, иначе в опере получим ошибку на frameset-ных страницах
s = r ? r.text : ''
} else {
// window.getSelection() в опере багнута, теряя переносы строк, поэтому используем document.getSelection()
// явное приведение к строке необходимо, потому что в webkit document.getSelection() возвращает объект, такой же как и window.getSelection()
s = String(d.getSelection());
// перебираем textarea и могущие иметь выделение input-ы и ищем в них выделенный текст.
if (!s) for (var i = 0, t = d.querySelectorAll('textarea,input[type=text],input[type=search],input:not([type])'), e; e = t[ i ]; i++) {
if (s = e.value.substring(e.selectionStart, e.selectionEnd)) break
}
};
// рекурсивно вызываем себя во всех фреймах
if (!s) for (var i = 0, f; f = w.frames[ i ]; i++) {
try {
if (s = arguments.callee(f)) break
} catch(e) {}
};
return s
};
Собственно, получение информации с другого сайта
Делать кроссдоменные запросы, как таковые, в букмарклете нельзя, но ситуацию спасает то что многие сервисы поддерживают JSONP. Фактически это просто способ получить в ответ на GET-запрос, js-файл содержащий вызов функции с именем заданным в параметре callback и в которую передаётся JSON-объект. Нечто подобное:showGoogleTranslate({responseStatus: "200"})
После загрузки этот js-файл вызовет предварительно созданную нами на странице функцию showGoogleTranslate и передаст ей объект содержащий нужную информацию. Как видим всё очень просто. И пара примеров для иллюстрации.
Получаем перевод выделенного текста (Text translation to Russian):
javascript: (function () {
var getSel = function (w) {
var s, d = w.document;
if (d.selection) {
var r = d.selection.createRange();
s = r ? r.text : ''
} else {
s = d.getSelection();
if (!s) for (var i = 0, t = d.querySelectorAll('textarea,input'), e; e = t[ i ]; i++) {
if (s = e.value.substring(e.selectionStart, e.selectionEnd)) break
}
};
if (!s) for (var j = 0, f; f = w.frames[j]; j++) {
try {
if (s = arguments.callee(f)) break
} catch(e) {}
};
return s
};
var txt = encodeURIComponent(getSel(window));
if (!txt || txt.length > 1900) {
window.open('http://translate.google.com/translate?u=' + escape(location.href) + '&hl=ru&langpair=auto|ru&tbb=1&ie=' + document.characterSet)
} else {
var ele = document.documentElement.appendChild(document.createElement('script'));
window.showGoogleTranslate = function (o) {
alert(o.responseStatus == 200 ? o.responseData.translatedText : o.responseStatus + ': ' + o.responseDetails);
ele.parentNode.removeChild(ele);
delete window.showGoogleTranslate
};
ele.src = 'http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=' + txt + '&langpair=|ru&callback=showGoogleTranslate&format=text'
}
})()
Другой пример, с комментариями. Получаем укороченную ссылку на текущую страницу (Create a short link with bit.ly):
javascript: (function () {
// генерим уникальное имя функции, которая будет вызываться из скрипта и создаём сам скрипт
var callback = ('js'+Math.random()).replace('.', 'n'), ele = document.documentElement.appendChild(document.createElement('script'));
// создаём функцию с уникальным именем
window[callback] = function (o) {
// обрабатываем полученный json
prompt('Укороченная ссылка:', o.status_code == 200 ? o.data.url : o.status_code + ': ' + o.status_txt);
// убираем скрипт и функцию
ele.parentNode.removeChild(ele);
delete window[callback];
};
// в конце задаём адрес скрипта, что вызывает его загрузку; такой вариант нормально работает и в старых версиях оперы
ele.src = 'http://api.bit.ly/v3/shorten?login=operafanuser&apiKey=R_3519a5e5f78017f55544b0b2053d4982&longUrl=' + encodeURIComponent(location.href) + '&format=json&callback=' + callback;
})()
И ещё один пример. Для использования в меню. Получаем размер файла находящегося на другом домене (Get file size):
javascript: (function () {
var formatSize = function (h) {
if (!h || isNaN(h = parseInt(h))) return 'неизвестно';
var add = ' (' + h + ' byte)',
kb = 1024,
mb = kb * kb,
gb = mb * kb;
if (h < kb) return h + ' B';
if (h < mb) return (h / kb).toFixed(1) + ' KiB' + add;
if (h < gb) return (h / mb).toFixed(1) + ' MiB' + add;
return (h / gb).toFixed(1) + ' GiB' + add
};
var formatDate = function (h) {
return h ? new Date(h).toLocaleString() : 'неизвестно'
};
var responseTime = function (s) {
return Math.round((new Date() - s) / 100) / 10 + ' сек'
};
var reqTimeout, url = %%22%l%%22;
if (!/^https?:/i.test(url)) {
alert('Протокол не поддерживается.');
return
};
var req = new XMLHttpRequest();
req.open('HEAD', url + '?', true);
req.onreadystatechange = function () {
if (this.readyState == 4) {
clearTimeout(reqTimeout);
alert('Файл: ' + url + '\n\nВремя отклика: ' + responseTime(start) + '\n\nРазмер: ' + formatSize(this.getResponseHeader('Content-Length')) + '\nДата: ' + formatDate(this.getResponseHeader('Last-Modified')) + '\n\nЗаголовки:\nStatus: ' + this.status + ' ' + this.statusText + '\n' + this.getAllResponseHeaders())
}
};
var start = new Date();
try {
req.send(null);
reqTimeout = setTimeout(function () {
req.abort()
},
10000)
} catch(e) {
var callback = ('js' + Math.random()).replace('.', 'n'),
ele = document.documentElement.appendChild(document.createElement('script'));
window[callback] = function (o) {
alert(o.ok && o.headers ? 'Файл: ' + url + '\n\nВремя отклика: ' + responseTime(start) + '\n\nРазмер: ' + formatSize(o.headers['content-length']) + '\nДата: ' + formatDate(o.headers['last-modified']) + '\n\nStatus: ' + o['status_code'] + (o.headers['server'] ? '\nServer: ' + o.headers['server'] : '') + (o.headers['content-type'] ? '\nContent-Type: ' + o.headers['content-type'] : '') + (o.headers['cache-control'] ? '\nCache-Control: ' + o.headers['cache-control'] : '') : 'Информация недоступна.');
ele.parentNode.removeChild(ele);
delete window[callback];
};
ele.src = 'http://json-head.appspot.com/?url=' + encodeURIComponent(url) + '&callback=' + callback;
}
})()

Mağruf ÇolakoğluZAHEK # Wednesday, September 1, 2010 8:21:36 PM
KiraSowingSadness # Sunday, September 5, 2010 7:47:54 PM
Когда удалили document.selection я чуть ядом на форуме не захлебнулся. Сам пришел к такому же решению. Надеялся в будущем кто-то найдет более хорошее... обидно ... досадно
Самая попа в том, что выделение может быть одновременно и в textarea и на странице.
A.RuzanovLex1 # Saturday, September 18, 2010 4:04:47 PM
There are many descriptions in English, about using jsonp. Here just some useful code examples. Text translation, creating a short link and getting file size.
Unregistered user # Friday, September 23, 2011 1:46:08 AM