Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

четверг, 18 февраля 2010 г.

Как скопировать из JavaScript-кода текст в буфер обмена?

Как-то раз я заметил одну приятную фишку: под textarea находился линк, копирующий ее содержимое в буфер обмена Windows. Выглядит фишка где-то так

Очень удобно" - подумал я. Следующая мысль была "нифига себе JavaScript и такое умеет?". (читаем дальше)

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

После этой мысли взялся я реализовать эту фичу (хотя заказчик о ней не просил). 5 раз хотел все бросить и все потому, что нифига не получалось. Возникала мысль "это невозможно" и я бил себя в ухо, а потом продолжал пробовать. Если не интересны детали - переходим сразу к рабочему варианту.

Если нужна поддержка только одного браузера InternetExplorer, то тут все просто. Пишем код
if (window.clipboardData) { 
        window.clipboardData.setData('Text', yourVariable)}
    }
И все... Проводник ласково спросит, можно ли дать доступ скрипту к буферу и если ответ будет положительный - скопирует...

Не просто оказалось реализовать доступ к буферу для Mozilla Firefox. А все потому, что она, видите ли, секьюрная очень а мануалы, ко всему, пишутся для простых случаев. Я видел в сети, как минимум, три варианта реализации. Перечислю их все, может кому-то и пригодится.

Первый (взят тут)
<script language="javascript">
        //To allow paste function to work in firefox you must:
        //write in url about:config
        //and change signed.applets.codebase_principal_support = true

        function init() {
            try{
                window.clipboard = new Clipboard();
            } catch (e) {
                alert("If you are using firefox please do the following :\n 1. Write in your url box : 'about:config'\n2. Change signed.applets.codebase_principal_support = true\n")
            }
        }

        // Must init the clipboard
        window.onload = init; 

        // Function to return the data in clipboard
        function getClipboardContents() {
            return window.clipboard.paste();
        }          
    </script>
Научил меня докапываться до настроек Firefox с помощью дырки "about:config". Скопировать текст так и не получилось.

Второй (взят тут) оказался платным, но в примере копирование удалось. Значит есть шанс!

Третий способ (описан тут) так же мне не помог.

Был еще один, но и он провалился.

Наконец я нашел одну библиотеку, которая тоже нифига не поддавалась, но через три часа экспериментов (уверенности придавал пример, который заработал под мозилкой!) мне удалось понять в чем дело.

Итак, что надо сделать:
1) Качаем отсюда последнюю версию либы и переписываем ее себе в проект. У меня это 4 файла и один тестовый, с которым я экспериментировал.
2) Дальше создаем тестовый (тот самый test.html) файлик с содержимым
<html>
        <head>
        <script src="/js/ZeroClipboard/ZeroClipboard.js" type="text/javascript"></script>
        <script language="JavaScript">
            var clip = null;

            function init() {
                clip = new ZeroClipboard.Client();
                clip.addEventListener('complete', function(client, text) {
                    alert("Copied text to clipboard: " + text);
                });
                clip.glue('copy_button');
                clip.setText(document.getElementById('text_area').value);
            }
 
        </script>
        </head>
        <body onLoad="init()">
            <textarea onChange="clip.setText(this.value)">Copy me!</textarea>
            <div class="my_clip_button" id="copy_button"><b>Copy To Clipboard...</b></div>
        </body>
    </html> 
3) Запускаем его у себя и проверяем, что все работает. Должна появиться формочка в которой будет текст, а снизу кнопка. При клике на кнопку выскочит alert, с сообщением из textarea, этот же текст должен содержаться и в буфере обмена.
Когда все заработало мы можем взяться за реальный код.

4) Я создал textarea и div-кнопку
<div id="informer_code">
        <textarea id="informer_code_area">Some text</textarea>
        <div class="js_link" id="copy_to_clipboard">Копировать в буфер обмена</div>
    </div>
5) в момент готовности документа инициализировать компоненту (jQuery код)
$(document).ready(function() {
        ZeroClipboard.setMoviePath( '/js/ZeroClipboard/ZeroClipboard.swf' ); // указываем где находится swf-файл.
        clip = new ZeroClipboard.Client(); // создаем инстанс компоненты
        clip.glue('copy_to_clipboard'); // подключаем кнопку
        clip.addEventListener('complete', function () { // обработчик сработает после копирования
            alert('Информер скопирован в буфер обмена...'); 
        });
    
        var copyToClipboard = function () { // метод копирования текста из textarea в компоненту, а при клике на кнопку и в буфер обмена.
           clip.setText($("#informer_code_area").val());
        }
        $(".informer_code").hide(); // спрятали контейнер с кнопкой после 
        $(".informer_code").css("visibility", "visible"); // делаем его видимым
  
        $("#informer_code_area").change(function() { // при изменении содержимого textarea записываем его в компоненту
            copyToClipboard();
        });
        copyToClipboard(); // записываем изначальный текст textarea в компоненту
    }
Проблема была в том, что кнопка изначально была скрыта "display:none" и библиотечка не смогла правильно инициализировать себя. Не зная этого, пришлось немного попыхтеть, пока случайно не протестил с видимой кнопкой. Потому если компонента изначально невидима - стоит заменить способ скрытия с "display:none" на "visibility:hidden", после инициализации компоненты поменять его на "visibility:visible" и спрятать как полагается ("display:none"). Либо инициализировать компоненту по принципу Singleton в момент первого доступа, когда компонента уже видна.

Хочу отметить еще один важный момент, с которым столкнулся в ходе отладки. Если выше по дереву DOM на ветках происходят какие-то "спрятали/показали", в результате чего offsetLeft и offsetTop исходного дива-кнопки меняется, то это ведет к рассинхронизации.
Компонента накрывает исходный див-кнопку сверху swf-кой которая и производит копирование в буфер обмена (ей-то можно). Эта swf-ка привязывается к кнопке в момент создания с помощью стилей:
element.style {
    height:17px;
    left:610px;
    position:absolute;
    top:596px;
    width:128px;
    z-index:99;
}
И больше не меняет своего положения. Если исходный див-кнопка поменял свое положение, то нам придется синхронизировать их так (jQuery код)
// то из за чего происходит смещение
    // отображаем панельку (исходный див-кнопка сместится ниже)
    var showInformerCode = function() {
        $("#show_informer_code").hide(); 
        $("#hide_informer_code").show(); 
        $("#informer_code").show();
        $("#informer_width").focus();
        fixCopyToClipboardDivPosition();
    }

    // прячем панельку (исходаный див-кнопка сметится выше)
    var hideInformerCode = function() { 
        $("#show_informer_code").show(); 
        $("#hide_informer_code").hide(); 
        $("#informer_code").hide();
        fixCopyToClipboardDivPosition();
    }
    
    // функция корректировки
    var fixCopyToClipboardDivPosition = function() {
        // находим див содержащий swf-ку 
        var parentDiv = $("#ZeroClipboardMovie_1").parent();

        // синхронизируем див с swf-кой и исходный див-кнопку
        parentDiv.css("top", $('#copy_to_clipboard')[0].offsetTop);
    }

Ну вот вроде как и все...

<!-- Ага! --> 
<!-- Если интересно как вставлять текст подобным образом - тогда идем -->
<a href="http://blog.cartercole.com/2009/10/awesome-syntax-highlighting-made-easy.html">сюда</a>

16 комментариев:

  1. Ага, флеш имеет доступ к большей функциональности чем джаваскрипт. Интереснее всё-таки на чистом js.

    ОтветитьУдалить
  2. Интересно! Ждем на НеформатеЦ ;)

    ОтветитьУдалить
  3. Привет, а вот что то непонятное.
    может кто подскажет где я упустил.

    код приведенный выше, да и вообще сам оригинал работает без вопросов.
    но мне нужно что бы он работал на вкладках, созданных динамически.
    И вот пока ставлю на статичных вкладках, все ок, а если вкладка создана
    tabs("add","help/index");
    то увы, ничего не могу сделать..

    есть идеи что я упустил?

    ОтветитьУдалить
  4. "пока ставлю" это я так понимаю вызываете
    clip = new ZeroClipboard.Client();
    clip.glue('div_name');

    В этот момент создается к дому подключается тег, который содержит флешку. размеры приобретает те еж что и у дива, накоторый лепится, а позиция его абсолютна. Если дива к этому времени нет или он невидимый, тогда возможны глюки.

    Попробуйте создать флеш кнопку (если она еще не создана) сразу после создания динамического табчика. Если Вы так и делаете, тогда посмотрите с помощью FireBug (к примеру) струкутру Dom вашей страничке и найдите где-то в конце тег с флешкой. Если она есть, тогда предстоит ответить на вопрос "почему она не на месте". Если ее нет, тогда вопрос "почему ее нет"...

    И опишите, что получилось.

    Спасибо Вам за вопрос.

    ОтветитьУдалить
  5. Привет!
    Спасибо за ответ.

    Вопрос с вкладками фактически остался открыт.
    Хотя проблему "обошел", просто подключаю JS прямо к загружаемому в вкладку файлу.

    На странице есть контекстное меню, к пунктам которого и "привязана" флешка.

    В принципе все работает отлично.

    Но так как контекстное меню может появляется в любом месте, где "тыкнул" пользователь, и в зависимости от этого меняется и копируемый контент, то я добавил функцию которая при добавлении объекта флеш, запоминает его ID и записывает в строку.
    А при определенном условии удаляет все созданные ранее объекты.

    в деле посмотреть тут: http://bs-catalog.com/
    код тут: http://code.google.com/p/biblequote-online/source/browse/trunk/www/js/bq_add_to_tab.js

    ОтветитьУдалить
  6. Привет. Отличная хау-ту, спасибо! Но вот за употребление слова компонент в женском роде хочется делать очень-очень больно. Не уподобляйся 1сникам ;)

    ОтветитьУдалить
  7. Пожалуйста. Если я сейчас исправлю, то следующим комментарием будет комментарий 1сницы суфражетки... Но все же согласен с тем, что написано не по русски.

    ОтветитьУдалить
  8. У меня у одного в Chrome криво считает отступ сверху?

    ОтветитьУдалить
  9. В Опере 11.10 не работает кнопка

    ОтветитьУдалить
  10. Да, жаль, что сразу не заработало. Как пытаетесь пофиксить?

    ОтветитьУдалить
  11. atapin,
    попробуйте на функцию создания табы повесить создание нового копиклипбоарда с задержкой в несколько секунд - setTimeout.

    У меня в хром всё чётко...

    ОтветитьУдалить
  12. Доброго дня, есть такой скрипт корзины simplecart(js). При нажатии на кнопку checkout происходит отсылка данных корзины на систему оплаты PayPal, то есть данные копируются в буфер, возможно ли внести изменения в код этой кнопки чтобы скопированные данные вставлялись в текстовое поле на странице?

    ОтветитьУдалить
  13. Думаю это можно сделать. Вы пробовали?

    ОтветитьУдалить
  14. Нет, я к сожалению не очень разбираюсь в Java, просто на форумах за это никто не берется ) вот я и подумала, что это невозможно

    ОтветитьУдалить
  15. Не выходит это все подключить к динамически создаваемым элементам.
    Кому нибудь удавалось такое?

    ОтветитьУдалить
  16. У меня у одного не получилось?

    ОтветитьУдалить