В этой статье я расскажу как создать с нуля виджет для сайта (на примере виджета опросов). Т.к. основной темой статьи все-таки является создание виджета, то создание самого опроса будет рассмотрено поверхностно.
Шаг 1. Создание таблиц в БД
В качестве базы данных будем использовать MySQL.
Создадим несколько таблиц:
polls — таблица с опросами
CREATE TABLE `polls` ( `poll_id` INT(11) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, `question` TEXT ) ENGINE = MYISAM AUTO_INCREMENT = 1 DEFAULT CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI;
answers — таблица с ответами для опросов
CREATE TABLE `answers` ( `answer_id` INT(11) unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, `answer` TEXT, `num` INT(11) NOT NULL DEFAULT '0', // порядковый номер ответа `poll_id_fk` INT NOT NULL DEFAULT '0', FOREIGN KEY (`poll_id_fk`) REFERENCES polls(`poll_id`) ) ENGINE = MYISAM AUTO_INCREMENT = 1 DEFAULT CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI;
Шаг 2. Создание страницы с опросом
Будем отталкиваться от предпосылки, что наш виджет должен быть произвольной ширины (в рамках разумного), чтобы его можно было встраивать в любые сайты, например, в центре текста по ширине статьи или в боковую часть сайта (обычно узкую). Поэтому при создании страницы с опросом старайтесь проектировать «резиновый» дизайн.
В данном примере ограничимся самым простым выводом опроса.
<?php // получаем через параметр номер опроса $poll_id = (int) (!isset($_GET["id"]) ? -1 : 0 + $_GET["id"]); // если что-то не то, выходим if ($poll_id <= 0) exit; $sql = mysql_query("SELECT * FROM polls WHERE poll_id = $poll_id"); // если ничего не выбралось, выходим if (!($sql && ($row = mysql_fetch_array($sql)))) exit; // сохраним вопрос в переменную для дальнейшего использования $question = $row['question']; // IE блокирует чужие cookie (через iframe), добавляйте эту строчку, если будете их писать header('P3P:CP="IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT"'); ?> <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <style type="text/css"> <?php // если передан параметр цвет фона, то задействуем его if (isset($_GET['bg_color'])) { $bg_color = $_GET['bg_color']; echo "html,body { background: #$bg_color; }"; } ?> </style> </head> <body onresize="resize_canvas()"> <?php // выводим опрос с ответами echo "<h1>$question</h1>"; $sql = mysql_query("SELECT * FROM answers WHERE poll_id_fk = $poll_id ORDER BY num ASC"); if ($sql) { while ($row = mysql_fetch_array($sql)) { $answer = $row['answer']; echo "<p>$answer</p>"; } } ?> <script type="text/javascript"> // этот скрипт сообщает родительскому окну высоту своего содержимого // с помощью механизма кросс-доменного обмена var parent_url = decodeURIComponent(document.location.hash.replace(/^#/, '')); function send(msg) { XD.postMessage(msg, parent_url, parent); } function resize_canvas() { send($('body').height()); } </script> </body> </html>
Т.к. пример у нас обучающий, то мы не делаем процедуру голосования, вывод результатов и т.д.
В тексте скрипта, как может показаться, много всего сходу непонятного, но все вопросы снимутся по прочтению следующих шагов. Отметим здесь только обработчик onresize, который посылает при изменении размера страницы высоту в родительское окно.
Шаг 3. Создание скрипта для встраивания виджета на сайт
Сегодня стандартом де-факто для большинства виджетов является механизм встраивания через iframe.
Для этого есть ряд причин:
1. Владельцы сайтов, которые решили встроить ваш виджет не хотят, чтобы чужой код получал доступ к его содержимому (это как минимум небезопасно).
2. Встроить ваш виджет через iframe очень легко и не требует дополнительных навыков и знаний.
3. Из-за возможных ошибок в вашем коде может слететь верстка сайта, встраивающего ваш виджет.
4. Если вы храните в cookies какую-то информацию, например, идентификатор проголосовавшего, то при встраивании виджета через iframe данные будут храниться в привязке к URL сайта виджета (а не сайта, в который он встроен), что может быть полезно, например, если тот же самый опрос висит и на другом сайте. Человек уже проголосовавший один раз будет и на другом сайте видеть свой ответ, а не голосовать каждый раз заново.
и т.д.
Создадим функцию, которая будет внедрять на странице сайта наш виджет. Обратите внимание, в код зашиты два идентификатора (для простоты чтения, их можно вынести как параметры). Это widget_container — контейнер, внутри которого будет создан фрейм, и сам фрейм widget_iframe.
function createWidget(config) { var Util = { extendObject: function(a, b) { for(prop in b){ a[prop] = b[prop]; } return a; }, proto: 'https:' == document.location.protocol ? 'https://' : 'http://' } var options = Util.extendObject({ id: 0, domain: "example.com", bg_color: "FFFFFF" }, config); options.widget_url = [Util.proto, options.domain, "/?", "id=", options.id, "&bg_color=", options.bg_color].join(""); options.widget_url += "#" + encodeURIComponent(document.location.href); Widget = { created: false, widgetElement: null, show: function() { if (this.created) return; this.widgetElement = document.createElement('div'); this.widgetElement.setAttribute('id', 'widget_container'); this.widgetElement.innerHTML = ' \ <iframe id="widget_iframe" src="' + options.widget_url + '" scrolling="no" width="100%" height="0" frameborder="0"></iframe>'; document.body.insertBefore(this.widgetElement, document.body.nextSibling); this.widgetElement.style.display = 'block'; this.created = true; } } XD.receiveMessage(function(message) { if (message.data > 0 && document.getElementById("widget_iframe")) { document.getElementById("widget_iframe").height = message.data; } }, Util.proto + options.domain); Widget.show(); } createWidget(widgetOptions);
Внутри функции описывается несколько объектов:
Util — помогает работать с параметрами.
Widget — сам объект нашего встраиваемого виджета.
XD — объект кросс-доменного обмена сообщениями (см. шаг 4, п. 2).
Как видно из кода, сначала создается элемент DIV, потом внутри него создается IFRAME, в который загружается страница с заданными в скрипте параметрами (по умолчанию это example.com/?id=0&bg_color=FFFFFF). Также устанавливается обработчик событий (получение сообщений от фрейма), который меняет высоту элемента, в данном случае растягивает виджет по высоте его содержимого.
Шаг 4. Решение проблем отображения
При встраивании виджета через iframe возникают определенные трудности.
1. Т.к. по сути в виджете загружен отдельный html-документ, то хочется как-то в него передавать свои настройки. Актуальным вопросом здесь являются стили, чтобы содержимое виджета вписывалось в дизайн сайта-потребителя. Можно реализовать передачу параметров через глобальную переменную, в нашем примере — это widgetOptions.
var widgetOptions = { id: 1, // id опроса bg_color: 'FF0000' // цвет фона };
После того, как переменная объявлена, загружаем наш скрипт, создающий виджет:
(function() { var script = document.createElement('script'); script.type = 'text/javascript'; script.async = true; script.src = "http://example.com/widget.js"; // путь скрипта из шага 3 document.getElementsByTagName('head')[0].appendChild(script); })();
2. Есть проблема растягивания iframe по высоте его содержимого, чтобы не было никаких полос прокруток, а все смотрелось гармонично.
Для этого воспользуемся небольшим готовым скриптом:
/* * a backwards compatable implementation of postMessage * by Josh Fraser (joshfraser.com) * released under the Apache 2.0 license. * * this code was adapted from Ben Alman's jQuery postMessage code found at: * http://benalman.com/projects/jquery-postmessage-plugin/ * * other inspiration was taken from Luke Shepard's code for Facebook Connect: * http://github.com/facebook/connect-js/blob/master/src/core/xd.js * * the goal of this project was to make a backwards compatable version of postMessage * without having any dependency on jQuery or the FB Connect libraries * * my goal was to keep this as terse as possible since my own purpose was to use this * as part of a distributed widget where filesize could be sensative. * */ // everything is wrapped in the XD function to reduce namespace collisions var XD = function(){ var interval_id, last_hash, cache_bust = 1, attached_callback, window = this; return { postMessage : function(message, target_url, target) { if (!target_url) { return; } target = target || parent; // default to parent if (window['postMessage']) { // the browser supports window.postMessage, so call it with a targetOrigin // set appropriately, based on the target_url parameter. target['postMessage'](message, target_url.replace( /([^:]+:\/\/[^\/]+).*/, '$1')); } else if (target_url) { // the browser does not support window.postMessage, so set the location // of the target to target_url#message. A bit ugly, but it works! A cache // bust parameter is added to ensure that repeat messages trigger the callback. target.location = target_url.replace(/#.*$/, '') + '#' + (+new Date) + (cache_bust++) + '&' + message; } }, receiveMessage : function(callback, source_origin) { // browser supports window.postMessage if (window['postMessage']) { // bind the callback to the actual event associated with window.postMessage if (callback) { attached_callback = function(e) { if ((typeof source_origin === 'string' && e.origin !== source_origin) || (Object.prototype.toString.call(source_origin) === "[object Function]" && source_origin(e.origin) === !1)) { return !1; } callback(e); }; } if (window['addEventListener']) { window[callback ? 'addEventListener' : 'removeEventListener']('message', attached_callback, !1); } else { window[callback ? 'attachEvent' : 'detachEvent']('onmessage', attached_callback); } } else { // a polling loop is started & callback is called whenever the location.hash changes interval_id && clearInterval(interval_id); interval_id = null; if (callback) { interval_id = setInterval(function(){ var hash = document.location.hash, re = /^#?\d+&/; if (hash !== last_hash && re.test(hash)) { last_hash = hash; callback({data: hash.replace(re, '')}); } }, 100); } } } }; }();
Из фрейма с нашим виджетом будем посылать его высоту (при инициализации и при изменении размера окна) в родительское окно. Скрипт на странице с виджетом будет получать это значение и растягивать окно с виджетом по высоте его содержимого.
<body onresize="resize_canvas()"> <script type="text/javascript"> var parent_url = decodeURIComponent(document.location.hash.replace(/^#/, '')); function send(msg) { XD.postMessage(msg, parent_url, parent); } function resize_canvas() { send($('body').height()); } </script> </body>
Шаг 5. Оптимизация и релиз
После того, как наш виджет сделан, хорошим тоном считается минимизировать javascript код, чтобы не было сильного увеличения времени загрузки сайта-потребителя, а также для уменьшение трафика самого сайта виджета.
Подробнее об этом можно почитать в статье на Хабре Оптимизация Javascript с помощью Google Closure Compiler.
Если у вас полезный виджет, которым пользуются многие сайты, рекомендую поддержать https протокол, чтобы пользователям, выбравшим защищенное соединение, не отображалось сообщение о содержании небезопасного контента.
В скрипте для вставки вашего виджета строка пути теперь будет выглядеть следующим образом:
script.src = (document.location.protocol == "https:" ? "https:" : "http:") + "//example.com/widget.js";
P.S.
Исходников данного примера нет, поэтому не могу их выложить. Если кто-то на основе этой статьи соберет готовый проект и захочет им поделиться — wellcome!
ссылка на оригинал статьи http://habrahabr.ru/post/169909/
Добавить комментарий