Букмарклеты или как скачать аудиофайлы с сайта vkontakte?

Вячеслав Гринин, December 12, 2010

Сегодня я собираюсь рассказать про одну интересную технологию, как мне кажется, с большим будущим. Называется эта технология bookmarklets(букмарклеты).
Букмарклет – это маленькая JavaScript-программа, оформленная в виде javascript-ссылки и вызываемая как браузерная закладка. Итак, мы знаем, что в атрибуте href гипертекстовой ссылки можно указать url любой существующей страницы, обычно url этот выглядит примерно так <a href=’http://address.org/path/index.html’>ссылка</a>, в данном случае http: – это указание протокола обмена, а все остальное – указание сервера, пути к файлу, и имени файла, к которому обратится браузер после клика на ссылке. Так вот, существует ряд протоколов, таких как, например, http: (протокол гипертекстового обмена), ftp:(протокол обмена файлами), file:(протокол загрузки локальных файлов пользователя).

Помимо этого существует еще один интересный протокол javascript:, который мы и будем использовать для создания букмарклетов. Выглядит это примерно так:

<a href=’javascript:alert(‘Hello, world’);’>букмарклет</a> при щелчке по этой ссылке, выполнится команда alert(‘Hello, world’); Проверьте, как это работает.

Попробуем теперь перетащить эту ссылку в панель закладок браузера, или щелкнув на ней правой кнопкой добавить ее в закладки. После того, как ссылка сохранена в закладки, можно в любой момент выбрать ее и выполнить.

Попробуем немного усложнить нашу закладку, пусть тепреь она выводит адрес документа из которого она вызвана. Вот этот букмарклет:

<a href=’javascript:alert(document.location);’>Адрес текущей страницы</a>.

Добавьте эту ссылку (Адрес текущей страницы) в закладки и вызовите ее, открыв предварительно в браузере какую-нибудь интернет-страницу, например сайт Яндекса. Вы увидите всплывающее оповещение с указанием адреса текущей страницы. Из этого можно сделать один очень важный вывод: букмарклет, вызванный из браузера, как закладка, выполняется в контексте текущей, открытой в браузере страницы. А этот факт дает нам неограниченный доступ к изменению данных на странице, и к извлечению из страницы любых данных, вплоть до полной замены содержимого этой страницы.

К сожалению, длина скрипта, сохраняемого в закладке ограничена. Причем, для разных браузеров она разная. Не углубляясь в различия между браузерами, можно сказать, что длина эта ограничена для различных браузеров от 488 до 2084 символов, что не может не огорчить. В принципе, для несложных букмарклетов нам хватит и этой длины, но что делать, если нам хочется большего? Что если мы хотим написать скрипт, который производит достаточно сложную обработку загруженной страницы, и в 2 кБ скрипта нам никак не уложиться?

Для этого можно использовать способ, заключающийся в следующем: сам букмарклет представляет собой небольшой скрипт, у которого есть лишь одно предназначение – загрузить основной скрипт и запустить его на выполнение.

Созданием этого скрипта мы сейчас и займемся. Но для начала замечу вот что. Какой бы скрипт мы ни создавали, практически всегда в нем мы будем объявлять создание новых функций и новых переменных. А создаваться они будут в контексте загруженной пользователем страницы, где до нас уже могут существовать какие-либо переменные и функции. Понятно, что мы никак не можем предугадать, какие имена будут у этих переменных, а значит, создавая свои собственные, мы можем случайно переопределить уже существующие переменные, внеся тем самым непредсказуемые изменения в логику работы пользовательских скриптов. Таким образом, запустив букмарклет, помимо его полезной работы, мы можем получить и совершенно непредсказуемые побочные явления, связанные с перопределением переменных.

Самый удобный способ избавиться от этих эффектов – применить анонимную функцию. Анонимные функции определяются также как и именованные, за исключением того факта, что им не присваивается имя.

Простое объявление функции:

function fun2(x) {
  return x + 1;
}

Объявление функции, как переменной:

var fun2 = function(x) {
  return x + 1;
}

К обеим этим функциям мы можем обратиться одинаково:

alert(fun1(5));

alert(fun2(5));

Мы получим абсолютно идентичное поведение.  Такая же анонимная функция объявляется так:

function(x) {
  alert(x);
}

Однако, если ее просто объявить, то толку от нее не будет никакого. Чтобы от любой функции была польза – ее надо вызвать. Вызов анонимной функции всегда совмещен с ее объявлением, для этого полностью все объявление функции берется в круглые скобки, после которого также в круглых скобках приписываются все ее аргументы вызова:

(function(x) {
  alert(x);
})(5);

А для функции без аргументов – вот так:

(function() {
  alert('Я функция!');
})();

К чему мы городили весь этот огород? А к тому, что новая область видимости переменных в JavaScript возникает только в одном единственном случае – при объявлении функции. Таким образом, если в функции объявить переменную, то ее имя не перекроется с таким же именем, но объявленным в глобальном контексте. Поясню на примере:

var a = 5;
(function(){
  var a = 10;
  alert(a); // alert(10)
})();
alert(a); // alert(5)

Как видим второй вызов alert дал значение 5, а не 10, потому что переменная a = 10 Объявлена в другом контексте видимости. В дальнейшем при создании букмарклетов мы всегда будем использовать анонимные функции – они обеспечивают гарантию от побочных явлений за счет добавления всего 17 лишних символов скрипта (function(){})(); а весь код встраивается между фигурными скобками.

А вот теперь пора переходить к анонсированному чуть ранее загрузчику скриптов. Делать это мы будем вот так:

var script = document.createElement('script');
script.src = 'http://script_url.js';
script.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(script);

Как видим здесь нет ничего сложного: при помощи createElement создается элемент SCRIPT, этому элементу задаются необходимые параметры src и type (в src указывается полный url загружаемого скрипта), и, наконец, скрипт при помощи appendChild добавляется в конец раздела HEAD страницы. После этого он тут же начинает загружаться с сервера, и сразу после загрузки – выполняется.

Замечу сразу, что текст скрипта всего букмарклета должен быть собран в единую строку без переносов, чтобы он выглядел как ссылка и его можно было добавить в закладки.

Покажем это на простом примере. Ниже приведен простой букмарклет, загружающий с сервера скрипт и выполняющий его:

<a href=”javascript:(function(){var script=document.createElement(‘script’); script.src=’http://easy4web.ru/samples/bookmarklet/3_loaded/script.js’; document.getElementsByTagName(‘head’)[0].appendChild(script)})();”>Загрузить букмарклет</a>

А вот и сам готовый букмарклет: Загрузить букмарклет просто перетащите его в закладки и запустите. Он загружает с адреса http://easy4web.ru/samples/bookmarklet/3_loaded/script.js скрипт в котором – очень простое содержимое:

(function(){
  alert('Hello, world!');
})();

Да, после запуска букмарклета, мы увидим все то же сообщение “Hello, world!”.

Перестанем, наконец, писать бесполезные букмарклеты и сделаем хоть что-нибудь полезное, чтобы оценить всю мощь и красоту букмарклетов.

Я собираюсь сделать несложный букмарклет, который позволит мне легко и удобно сохранять аудиозаписи с сайта vkontakte.ru. Покопавшись немного в исходниках HTML-страниц сайта, я обнаружил, что при нажатии кнопки PLAY любой из аудиозаписей, происходит вызов функции operate, в которую в качестве параметров передаетсся либо url mp3-файла аудиозаписи, либо несколько параметров, указывающих на имя сервера медиаконтента, ID пользователя и ID файла аудиозаписи из которых путем конкатенации легко составить полный url аудиофайла.

Таким образом, моя идея заключается в следующем: после запуска букмарклет ищет на странице все кнопки PLAY (обычно они имеют class=’playimg’ или class=’play’), затем рядом с каждой такой кнопкой создает ссылку на аудиофайл, url к которому скрипт извлекает из атрибута onClick кнопки PLAY путем несложных текстовых преобразований.

Как видим, здесь напрямую придется работать с DOM-структурой, а мне очень не хотелось бы писать на низкоуровневом JavaScript все эти поиски элементов, извлечение из них атрибутов и создание новых элементов в существующем дереве DOM. Я собираюсь использовать для этого фреймворк jQuery.

Подгрузку jQuery я собирался делать стандартным способом при помощи createElement и appendChild, однако столкнулся вот с какой трудностью – после команды appendChild скрипт начинает загружаться, и нет никаких гарантий по времени его загрузки, в любом случае если сразу после appendChild попытаться обратиться к объекту jQuery, то выскочит ошибка “jQuery not defined“. Возник вопрос, как отследить момент, когда jQuery уже загрузился и можно начинать с ним работать?

Я решил обойтись собственным рукотворным решение, которое заключается в следующем: сразу после appendChild я запускаю цикл отслеживания, который каждые 0,1 сек проверяет, существование объекта jQuery, и как только объект появляется, загрузчик прекращает цикл проверки и вызывает функцию обратного вызова, в теле которой и будет происходить вся обработка страницы.

Также мой загрузчик скриптов перед тем как загружать какой-либо скрипт, проверит, а существует ли уже в DOM-структуре  script с совпадающим атрибутом src? И если такой скрипт уже есть то загрузка нового произведена не будет.

Чтобы не утомлять читателя подробным описание его работы, я просто приведу готовый скрипт загрузчика, оснащенный необходимыми комментариями:

var easybml = {
  // загружает скрипт
  loadJS: function(url) {
    var scripts = document.getElementsByTagName('script'),
    alreadyLoaded = false;
    for(var i=0; i<scripts.length; i++) {
      if(scripts[i].src == url) {
        alreadyLoaded = true;
        break;
      }
    }
    if(!alreadyLoaded) {
    var script = document.createElement('script');
    script.src = url;
    script.type = 'text/javascript';
    document.getElementsByTagName('head')[0].appendChild(script);
  }
},

// Загружает jQuery с заданного ресурса, после инициализации jQuery вызывает
// событие complete
loadJQuery: function(src, complete) {
  easybml.loadJS(src);
  // если передана функция обратного вызова, то сохраняем указатель на нее
  // и запускаем циклическую проверку инициализации jQuery
  if(complete) {
    easybml.prv.complete = complete;
    easybml.prv.checkJQuery();
  }
},

// секция закрытых данных, к этим методам обращаться не надо
prv: {
  complete: null,
  // циклически проверяет загружен ли jQuery, и когда он загружен
  // вызывает функцию easybml.prv.complete
  checkJQuery: function() {
  if (typeof jQuery == 'undefined') {
    setTimeout('easybml.prv.checkJQuery()', 100);
  } else {
    easybml.prv.complete();
  }
}
}
};

(function(){
  // загружаем jQuery, если он еще не загружен
  if (typeof jQuery == 'undefined') {
    easybml.loadJQuery('http://easy4web.ru/samples/bookmarklet/5_jquery/jquery.php', function() {
    // после загрузки и инициализации jQuery становятся доступными все
    // методы. Присоединяем к документу обработчик события onclick
    $(document).ready(function() {
      $("body").click(function() {
          alert("Hello world!");
        });
      });
    });
  }
})();

Функция easybml.loadJS(url) загружает скрипт с адреса url, если скрипт с таким же src еще не был загружен.

Функция easybml.loadJQuery(src, complete) загружает скрипт фреймворка jQuery с адреса src, и после его успешной загрузки и инициализации вызывает функцию обратного вызова complete.

Как видим в данном примере вся полезная функциональность скрипта заключена в том, что он к элементу BODY документа присоединяет обработчик события OnClick, который при клике на странице выводит alert(‘Hello, world!’).

И наконец, остался последний шаг. Вот она – полная версия скрипта, позволяющего загрузить аудиозаписи с сайта vkontakte.ru:

var easybml = {
// загружает скрипт
loadJS: function(url) {
  var scripts = document.getElementsByTagName('script'),
  alreadyLoaded = false;
  for(var i=0; i<scripts.length; i++) {
    if(scripts[i].src == url) {
      alreadyLoaded = true;
      break;
    }
  }
  if(!alreadyLoaded) {
    var script = document.createElement('script');
    script.src = url;
    script.type = 'text/javascript';
    document.getElementsByTagName('head')[0].appendChild(script);
  }
},

// Загружает jQuery с заданного ресурса, 
// после инициализации jQuery вызывает событие complete
loadJQuery: function(src, complete) {
  easybml.loadJS(src);
  // если передана функция обратного вызова, то сохраняем указатель 
  //на нее и запускаем циклическую проверку инициализации jQuery
  if(complete) {
    easybml.prv.complete = complete;
    easybml.prv.checkJQuery();
  }
},

// секция закрытых данных, к этим методам обращаться не надо
prv: {
  complete: null,
  // циклически проверяет загружен ли jQuery, и когда он загружен
  // вызывает функцию easybml.prv.complete
  checkJQuery: function() {
    if (typeof jQuery == 'undefined') {
      setTimeout('easybml.prv.checkJQuery()', 100);
    } else {
      easybml.prv.complete();
    }
  }
}
};

(function(){
  function clearQuotes(str) {
  return jQuery.trim(str).replace(/\'|\"/g,'');
}
function getMP3url(attr) {
  var parts = attr.split(",");
  if(parts.length == 3) {
    return clearQuotes(parts[1]);
  } else if(parts.length == 5) {
    return 'http://cs' + clearQuotes(parts[1]) + '.vkontakte.ru/u' + 
clearQuotes(parts[2]) + '/audio/' + clearQuotes(parts[3]) + '.mp3';
  } else if(parts.length == 2) {
    return parts[0];
  }
return parts[3];
}
// загружаем jQuery, если он еще не загружен
if (typeof jQuery == 'undefined') {
  easybml.loadJQuery('http://easy4web.ru/samples/bookmarklet/
5_jquery/jquery-1.4.4.min.js', function() {
    $(document).ready(function() {
      $(".playimg").each(function(n, el) {
        var container = $(el).parent();
        container.width('auto');
        var clickAttr = $(el).attr("onclick").toString();
        container.append(("<a href='" + getMP3url(clickAttr) + 
"' border='0' target='_blank'><img src='http://easy4web.ru/samples/
bookmarklet/6_saveaudio/saveicon.png' border='0'></a>"));
      });

      $(".play").each(function(n, el) {
        var container = $(el).parent();
        container.width('auto');
        var valueAttr = container.find('input').attr('value').toString();
        container.append(("<a href='" + getMP3url(valueAttr) + "' 
border='0' target='_blank'><img src='http://easy4web.ru/samples/
bookmarklet/6_saveaudio/saveicon.png' border='0'></a>"));
      });
    });
  });
}
})();

Здесь используется ряд вспомогательных функций:

clearQuotes(str) – Очищает строку str от обрамляющих пробелов и скобок,

getMP3url(attr) - Извлекает из атрибута attr собственно сам url аудиофайла,

Ну и наконец после объявления этих функций начинается непосредственно полезная работа скрипта – поиск на странице кнопок PLAY и добавление к ним ссылок на скачивание.

Полную версию букмарклета я разместил по адресу http://easyapi.ru/bml/vksaveaudio/.

Пользуйтесь на здоровье.

UPD 14.04.2011.
Сайт vkontakte изменил способ рендеринга страниц, содержащих проигрыватель audio-файлов. Они унифицировали отображение контейнера проигрывателя и теперь не требуется дополнительных плясок с бубном для различного рода страниц – теперь аудио-проигрыватель везде рендерится абсолютно одинаково, что дает возможность упростить скрипт. Ниже я приведу только текст основной функции скрипта, так как изменился только он. Скрипт стал короче, из него убраны все ветвления оператором if:

(function(){
    function getMP3url(attr) {
        var parts = attr.split(",");
        return parts[0];
    }
    if (typeof jQuery == 'undefined') {
        easybml.loadJQuery('http://easyapi.ru/bml/vksaveaudio/jquery-1.4.4.min.js', function() {
            $(document).ready(function() {
                $(".play_new").each(function(n, el) {
                    var container = $(el).parent().parent();
                    container.width('auto');
                    var valueAttr = container.find('input').
attr('value').toString();
                    container.append(("<a href='" + 
getMP3url(valueAttr) + "' border='0' target='_blank'>
<img src='http://easyapi.ru/bml/vksaveaudio/saveicon.png' 
border='0'></a>"));
                });
            });
        });
    }
})();

Кросдоменная передача данных между html-страницами

Вячеслав Гринин, April 29, 2010

Итак, представим себе ситуацию, что на некотором сайте в некоторой форме есть поле, в которое нужно ввести логин пользователя, но не свой собственный, а чужой логин, предположим, пользователя, которого надо добавить в друзья или в черный список. Но посетитель может не помнить наизусть, как пишется этот логин, а потому мы сделаем так, чтобы он мог выбрать его из списка, причем список этот должен открыться в отдельном окне и там помимо списка логинов пользователей должны отображаться еще и их фотографии, ФИО, возраст и т.д. Предположим, что мы даже создали такую страницу со списком пользователей. Возникает вопрос – как передать из одного окна браузера в другое окно некоторые данные (в данном случае это – логин пользователя)?

Справочник по JavaScript и объектной документной модели DOM говорит нам, что для открытия нового окна нужно использовать метод window.open(), а для доступа из “дочернего” окна в “родительское” (то есть то, которое и породило новое окно) нужно использовать указатель opener. Рассмотрим этот факт на примере:

index.htm

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <title>Главное окно</title>
 <script>
 function openWindow() {
 window.open("http://easy4web.ru/samples/transfer/popup.htm",
    "contents", "toolbar=no", "status=no");
 }
 </script>
 </head>
 <body>
 <input type="button" onclick="openWindow()"
    value="Показать диалог"/><br />
 <input type="text" id="data" />
 </body>
</html>

popup.htm

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>Диалоговое</title>
 <script>
 function transferData() {
 opener.document.getElementById("data").value =
    document.getElementById("inp").value;
 window.close();
 }
 </script>
 </head>
 <body>
 <input type="text" id="inp" name="inp"/>
 <input type="button" value="OK" onclick="transferData()"/>
 </body>
</html>

В главной странице мы видим кнопку, по нажатии на которую открывается новое окно, и TEXTBOX с айдишником data, в него-то и будет попадать текст, введенный в диалоговом окне.

А вот и диалоговое окно, в нем мы видим TEXTBOX и кнопку по нажатию на которую текст введенный в текстовое поле присваивается текстовому полю data из главного окна. А доступ к нему мы и получаем при помощи указателя opener.

Вот здесь (Передача данных между окнами в рамках одного домена) вы можете протестировать работу этого алгоритма.

Здесь все просто, и я не стал бы писать эту статью, если бы хотел рассказать только про это.

А хочу я теперь рассказать о том, как быть если окна эти расположены в разных доменах. Когда такое может произойти? Предположим, есть Ваш форум, а есть специализированный сервис для загрузки и хранения фотографий и вот теперь владелец форума хочет договориться с владельцем фото-сервиса, что на форму он разместит кнопку “Добавить изображение”, которая будет открывать окно созданное в рамках фото-сервиса, в этом окне пользователь загрузит фотографии, а затем по нажатию кнопки “ОК” ссылки на фотографии скопируются в окно редактирования сообщения на форуме. Как видим, здесь есть два окна в разных доменах и нам нужно передать текст из одного окна в другое.
“Что тут сложного?” – спросите вы. А давайте попробуем.

Невозможность передачи данных между окнами в разных доменах

При попытке передачи данных между окнами получим ошибку Error: Access is denied.. Происходит это потому что прежде чем получить доступ к любому методу или свойству объекта opener браузер сравнит домен, в котором существует этот объект и домен, из которого происходит вызов собственно метода или свойства объекта opener. И если доменные имена не совпадают, будет возбуждено исключение “Доступ запрещен”.

Решить эту проблему можно. И мы решим ее без использования каких-либо серверных технологий, только силами JavaScript. Это становится возможным, если мы узнаем еще вот какую тонкость. При изменении свойства location.href в порожденном окне в него загрузится страница, заданная ссылкой, но значение указателя opener не изменится, он так и будет продолжать указывать на породившее его главное окно. А теперь представим себе, что мы в диалоговое окно загрузили все ту же главную страницу, или любую другую но с того же домена, где лежит главная страница, а после этого обратились к объекту opener, теперь домены диалогового окна и объекта opener снова совпадают, а значит исключение возбуждено не будет.

А теперь – время для исходников, иллюстрирующих пример:

index.htm

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>Главное окно</title>
 <script>
 function openWindow() {
 var par = "?loc=" + location.href + "&id=data";
 window.open("http://easyapi.ru/easy4web/transfer/popup2.htm" + par, 
    "contents", "toolbar=no", "status=no");
 }
 function getDataFromUrl() {
 var txt_id, data;
 var url = location.href;
 var query = url.split("?")[1];
 if(query) {
 var params = query.split("&");
 for(var i = 0; i < params.length; i++) {
 var keyval = params[i].split("=");
 if(keyval[0] == "data") {
 data = keyval[1];
 }
 if(keyval[0] == "id") {
 txt_id = keyval[1];
 }
 }
 }
 if(txt_id && data) {
 opener.document.getElementById(txt_id).value = data;
 close();
 }
 }
 getDataFromUrl();
 </script>
 </head>
 <body>
 <input type="button" onclick="openWindow()" 
    value="Показать диалог"/><br/>
 <input type="text" id="data" />
 <script>
 getDataFromUrl();
 </script>
 </body>
</html>

Итак, здесь мы видим, что процедура openWindow() все также открывает диалоговое окно, однако теперь она передает ему параметры: loc – указывающий на URL самой порождающей страницы; id – айдишник текстбокса, в который будет вставлен текст из дочернего окна.
А еще мы видим, что после загрузки контента главной страницы вызывается функция getDataFromUrl(), которая проверяет, есть ли GET-параметры id и data (айдишник текстбокса и текст, который мы в него будем вставлять). Параметры эти при изначальной загрузке страницы не заданы, а сначит при первой загрузке страницы код обновляющий содержимое текстбокса не выполнится. А когда он выполнится мы узнаем после того, как рассмотрим исходники диалогового окна.

popup.htm

<html xmlns="http://www.w3.org/1999/xhtml">
 <head>
 <title>Диалог</title>
 <script>
 function transferData() {
 var url = location.href;
 var query = url.split("?")[1];
 if(query) {
 var params = query.split("&");
 for(var i = 0; i < params.length; i++) {
 var keyval = params[i].split("=");
 if(keyval[0] == "loc") {
 var loc = keyval[1];
 }
 if(keyval[0] == "id") {
 var txt_id = keyval[1];
 }
 }
 }
 if(loc && txt_id) {
 location.href = loc + "?id=" + txt_id + 
    "&data=" + document.getElementById("inp").value;
 }
 }
 </script>
 </head>
 <body>
 <input type="text" id="inp" name="inp"/>
 <input type="button" value="OK" onclick="transferData()"/>
 </body>
</html>

Здесь при клике по кнопке выполнится функция transferData(), которая сначала извлечет из адресной строки параметры loc и id, те самые, которые мы передали окну при его порождении. И если эти параметры есть – присвоим location.href адрес страницы loc(главной страницы), а в GET-параметры ей передадим id и data(айдишник текстбокса и данные, которые мы в него запишем). При этом в текущее окно (диалоговое) загрузится содержимое главнйо страницы и выполнится та самая функция getDataFromUrl() которая на этот раз извлечет все необходимые параметры из GET-строки и выполнит метод getElementById() объекта opener.document.

Вот и весь секрет кросдоменной передачи данных между окнами.

Вот здесь (Передача данных между окнами в разных доменах) можно протестировать работу вышеприведенного алгоритма.

Замечу, что у всех приведенных здесь решений есть существенный недостаток – корректно передаются только символы английского алфавита, цифры и знаки препинания. Решение этой проблемы оставлю на одну из следующих статей.

DOM: вытаскиваем текст вне тегов

Вячеслав Гринин, March 23, 2010

Анализируя поисковый трафик, я обнаружил вопрос “вытащить текст вне тегов”, делюсь рецептом. Собственно, каждая ситуация уникальна, но в общем случае, нужно отыскать в DOM-структуре некий строго определенный элемент и уже относительно него отыскать нужный нам текст.

В нашем случае документ представляет собой два дива с заданными строго идентификаторами, между которыми расположен искомый текстовый элемент.  При помощи getElementById мы на ходим элемент div1, а затем при помощи nextSibling добираемся до текстового узла, ведь он стоит следующим в DOM-структуре. Получить его значение можно при помощи свойства nodeValue.

Вот и весь рецепт.

<html>
<head>
 <title>Hello!</title>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
 <script type="text/javascript">
 function getText()
 {
 var div1 = document.getElementById("div1");
 div1.nextSibling.nodeValue = "Поменял текст";
 }
 </script>
</head>
<body>
<div id="div1">Предшествующий блок</div>
Это текст я хочу получить
<div id="div2">Замыкающий блок</div>
<input type="button" value="Start!" onclick="getText()">
</body>
</html>
Поиск по блогу:
Подписаться:
Популярные:
Облако тегов:
Разное:
Счетчик: