My programs for Opera

Using jsonp in bookmarklets

, ,

Использование 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;
    }
})()

Descriptions in opera:plugins and moreNoAds. (NoScript+AdBlock)/2

Comments

Mağruf ÇolakoğluZAHEK Wednesday, September 1, 2010 8:21:36 PM

Any english translation? smile

KiraSowingSadness Sunday, September 5, 2010 7:47:54 PM

>Ну и последний оставшийся вариант, это перебор всех input-ов и textarea и поиск в них выделенного текста. Способ не идеальный, т.к. реальное выделение может оказаться и в другом input-е, но другой альтернативы похоже нет.

Когда удалили document.selection я чуть ядом на форуме не захлебнулся. Сам пришел к такому же решению. Надеялся в будущем кто-то найдет более хорошее... обидно ... досадно

Самая попа в том, что выделение может быть одновременно и в textarea и на странице.

A.RuzanovLex1 Saturday, September 18, 2010 4:04:47 PM

z@h3k
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

Анонімний writes: People deserve wealthy life time and business loans or car loan can make it much better. Just because people's freedom relies on money.

Write a comment

New comments have been disabled for this post.