Пишем встраиваемый виджет на нативном javascript и php

май 21 , 2016
Демо Исходники

Встраиваемые виджетыКаждый раз подключая на своих сайтах готовое решение от сервисов вроде disqus или google-аналитики, я удивляюсь простоте интеграции достаточно сложного функционала. Поставил в код 3 строчки javascript-кода - и у тебя уже развернулся блок с комментариями. Еще 2 строчки - и доступна аналитика от гугла или яндекса. Конечно, никакого волшебства при этом не происходит, те самые 3 строчки кода подтягивают с удаленного сервера весь нужный код, и по сути разворачивают небольшое веб-приложение на страницах Вашего сайта. Но как это устроено внутри и как это сделать самому? Разобраться с этим было достаточно интересно, и в итоге у меня получился небольшой виджет, который работает именно по такой простой схеме встраивания и при этом еще выполняют некоторые полезные вещи. Далее подробности.


Зачем нужны виджеты?

Здесь все просто: допустим, у нас есть какой-то неимоверно крутой сервис. Он настолько крут, что мы хотим поделиться нашими возможностями со всем миром (а может, и продать их). Но здесь всплывают несколько моментов. Во-первых, мы не готовы предоставлять открыто наши исходники. Во-вторых, нужна максимально простая схема встраивания нашего функционала в сторонние сайты. В-третьих, мы хотим, чтобы наши пользователи были уверены, что использование нашего сервиса ничем не навредит их сайту.

Для этой цели хорошо подходят iframe-ы. Это фактически отдельные html-страницы, которые встраиваются в другие страницы, при этом не имея доступа к своему "родителю". То есть в iframe можно встроить наше веб-приложение, которое будет спокойно выполнять свою работу, не вмешиваясь в основной сайт. А что именно будет делать Ваш виджет, строить систему комментариев на странице, показывать прикольную игрушку или рисовать интерактивный рекламный баннер - дело исключительно Вашего вкуса и фантазии.

В этой статье на игрушку мы замахиваться не будем, а рассмотрим, как создать встраиваемый виджет для определения погоды. Конечно, для этого есть много различных погодных сервисов, но нам неинтересно воспользоваться готовым решением. Хочется создать простенький виджет своими руками, чтобы понять, как это вообще работает и в дальнейшем применить знания уже для каких-то полезных вещей. Заодно попутно освежим знания в нативном javascript и php.


Что будем делать?

Мы создадим виджет, который рисует форму с прогнозом погоды на завтра. В качестве примера возьмем только температуру, но как увидите дальше, легко можно будет добавить и другие данные. В виджете будет располагаться информация о температуре на завтра, раскрывающийся список с возможностью выбрать город. Кнопка Обновить будет отправлять запрос на сервер, который в свою очередь обращается на сторонний сервис для определения погоды и возвращает нам нужную инфу. Эти данные мы обрабатываем и возвращаем в браузер клиенту. Где уже клиентская часть виджета перехватывает информацию и выводит ее пользователю.

Выглядит это проще, чем звучит - можете сразу посмотреть, что в итоге получится - Демо страницы с виджетом


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

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

Итак, вернемся. Люди узнают погоду, возможно, даже не задумываясь о том, что фактически пользуются другим сервисом. Но все довольны и счастливы, Вы получаете посетителей на свой сайт, клиенты - нужную информацию.

Здесь нужно небольшое уточнение: смысл виджетов в том, чтобы предоставлять уникальный, удобный, интересный функционал. Мы не даем в нашем примере какую-то особенную информацию. Погоду можно узнать где угодно. Но в познавательных целях, так как мы сами пока ничего не умеем делать, возьмем данные по погоде с сервиса Прогноз погоды в России и СНГ. Они любезно предоставляют бесплатный api, коим мы и воспользуемся.


По технологиям

Тоже ничего сложного. Про iframe я уже проговорился. javascript-код будем писать на vanillaJS, без единой дополнительной библиотеки. Даже без jQuery для манипуляции DOM. Наша цель еще состоит и в том, чтобы полученный виджет был максимально легковесным, и ни к чему тащить в браузер пользователя мешок дополнительных библиотек для облегчения своей работы. Поэтому вспомним, как работать с DOM и отправлять ajax-запросы на нативном javascript, и наши клиенты скажут нам спасибо. Серверная часть - пара десятков строк кода на php.


Пишем код. Создаем базовую страницу

Страницу, на которой будет располагаться наш будущий виджет. Напишем там пару строчек текста о том, какие мы классные в плане умения прогнозировать погоду на завтра. Это будет файл index.html - обычная html-страничка. В секции head напишем так:

    Webdevkin. Демонстрация виджета Погода
	
	

В секции body вот так:

	

Погода в городах России

Здесь мы расскажем Вам про погоду в разных замечательных городах нашей страны!

Подписывайтесь на наши обновления и Вы всегда будете узнавать погоду вовремя!

Что здесь из интересного? В файле css/style.css будем хранить стили для страницы (не для виджета!). И в нужном месте создаем iframe. В атрибуте src iframe-a ставим ссылку на виджет, в нашем случае - https://webdevkin.ru/examples/weather/widget/widget.html. Мы убираем у него границу и возможность скроллинга и задаем руками размеры виджета. Конечно, интереснее создать адаптивный, но до этого доберемся позже.

Стили для страницы, без особых изысков:

    body {
    	font-family: Arial;
    	font-size: 14px;
    }
    
    .wrapper {
    	width: 960px;
    	margin: 0 auto;
    }
    
    .weather-widget-container {
        margin: 30px 0;
    }

Заготовка для виджета

Виджет представляет из себя обычный html-документ. В чем отличие? Первое - мы не будем заморачиваться с лишними мета-тегами, потому как поисковикам наш виджет не нужен. И второе - и стили, и js-код мы напишем прямо в коде html-страницы. Нам это нужно, чтобы сделать загрузку виджета максимально быстрой, а уменьшение числа запросов к веб-серверу в этом нам поможет. Все, что нужно, мы загрузим одним файлом.

В секции head у нас будет такой код:

    
    

В секции body:

    
        
        
    

Рассмотрим эти блоки подробнее.


Разметка для виджета

    
Температура на завтра °C

Здесь обычная форма. Сверху небольшая панель, где мы будем выводить температуру. Дальше select с выбором города. Значения value у городов взяты с сервиса meteoservice.ru, где именно, расскажу позже, в разделе, где будем непосредственно получать данные о погоде. И в конце кнопка Обновить. Верстка у нас будет на классах, айдишники проставлены тем элементам, доступ к которым нужен из кода javascript. Префикс wbd- (от webdevkin) используется для удобства, чтобы не путать разметки виджета и основной страницы.


Стили для виджета

    html, body, body * {
        margin: 0;
        padding: 0;
    }

    .wbd-widget, .wbd-widget * {
        box-sizing: border-box;
        font-family: Ubuntu;
    }

    .wbd-widget {
        border: solid 1px #333;
        padding: 20px;
        color: black;
    }

    .wbd-widget__info {
        color: steelblue;
        font-size: 16px;
        text-align: center;
    }

    .wbd-widget__form-label {
        display: block;
        line-height: 30px;
    }

Стили для виджета максимально просты - Вы можете написать их под себя, как угодно. Не забываем, что стили ставим прямо в html-файл виджета в раздел head - style.


javascript-код виджета.

Напишем класс, в котором будут методы инициализации, привязки событий и получения данных с сервера. Не забываем, что код будем писать на нативном js, так как не хотим обременять пользователей лишним грузом библиотек.

    var WeatherWidget = (function() {
        
        // Для выполнения ajax-запросов
        var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;

        // Конструктор виджета
        function Widget() {
            this.url = 'https://webdevkin.ru/examples/weather/widget/widget.php';
            this.ui = {
                updateBtn: null,
                citySelect: null,
                temperatureSpan: null
            };

            this.init();
        }

        // Обновление данных о погоде
        Widget.prototype._updateData = function(e) {
            // Отправка ajax-запроса на сервер
        }

        // Инициализация компонентов ui
        Widget.prototype._initUI = function() {
            this.ui.updateBtn = document.getElementById('wbd-widget-update');
            this.ui.citySelect = document.getElementById('wbd-widget-city');
            this.ui.temperatureSpan = document.getElementById('wbd-widget-temperature');
        }

        // Привязывание событий
        Widget.prototype._bindHandlers = function() {
            this.ui.updateBtn.addEventListener('click', Widget.prototype._updateData.bind(this));
        }

        // Инициализация виджета
        Widget.prototype.init = function() {
            this._initUI();
            this._bindHandlers();
            this._updateData();
        }

        // Возвращаем класс виджета
        return Widget;
    })();

    new WeatherWidget();

Что здесь происходит? Мы создаем модуль на основе замыкания WeatherWidget, внутри оного пишем класс Widget, в прототип которого закидываем нужные методы и возвращаем его из замыкания. Последней строкой мы создаем экземпляр виджета - new WeatherWidget();

Переменная XHR нужна для выполнения ajax-запросов. В конструкторе инициализируем свойство url, по которому будем получать данные о погоде. Также есть объект ui, в котором хранятся 3 поля - те самые элементы DOM, к которым нам нужен доступ из js-кода. Инициализируются они позже, в методе _initUI(). Метод init() вызывает инициализацию ui, привязку событий и обновление данных с сервера (мы же хотим, чтобы при отрисовке виджета пользователь сразу видел погоду без нажатия на Обновить). В _bindHandlers привязывается метод _updateData к клику на кнопку Обновить. Это единственная "интерактивная" часть нашего виджета.

Обратите внимание на конструкцию Widget.prototype._updateData.bind(this). Здесь явным образом привязываем контекст - нам нужно, чтобы в методе _updateData this всегда указывал на экземпляр класса Widget. Сейчас будет видно, зачем - рассмотрим код метода _updateData


Получение данных с сервера, метод _updateData

    // Обновление данных о погоде
    Widget.prototype._updateData = function(e) {
        e && e.preventDefault();

        var xhr = new XHR(),
            city = this.ui.citySelect.value,
            temperatureSpan = this.ui.temperatureSpan,
            data = 'city=' + city,
            resp;

        xhr.open('POST', this.url, true);
        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        xhr.send(data);

        xhr.onreadystatechange = function() {
            if (this.readyState != 4) return;
            if (this.status != 200) {
                console.log('Request error');
                return;
            }
            resp = JSON.parse(this.responseText);
            temperatureSpan.innerHTML = resp.temperature;
        }
    }

Обращаю внимание, что в параметры метода передаем свойство e (event). Оно нужно, чтобы сделать preventDefault в самом начале, дабы не перезагрузить наш виджет (иначе сработает submit формы).
Но делаем мы это такой строчкой
e && e.preventDefault();
Эта дополнительная проверка на наличие параметра e нужна, потому что когда мы вызываем this._updateData(), параметр e будет равен undefined, и соответственно e.preventDefault() выкинет исключение.

После этого мы создаем нужные переменные, объект для ajax-запроса, выбранный город и DOM-элемент, в который выводим значение температуры, полученной с сервера. data будет отправляться в ajax-запросе, resp - это ответ от сервера. Дальше мы открываем соединение и отправляем данные на сервер. Дожидаемся ответа 200 от сервера и выводим полученную температуру в span. Условимся, что от сервера приходит json-строка в формате {"code": "success", "temperature": "диапазон температур"}. Традиционно обработку ошибок оставлю на Вашу совесть, все делают по-разному. И наконец, нам осталось узнать, как получить данные о погоде с нашего сервера.


Как узнавать погоду?

Как я уже упоминал, мы воспользуемся сайтом meteoservice.ru. Он предоставляет возможность получить xml-файл с данными о погоде по любому гододу СНГ. Например, по этой ссылке доступна погода по Москве. С нашего сервера мы будем запрашивать этот файлик curl-ом, парсить его на нашей стороне, извлекать данные о температуре и возвращать ее обратно в браузер. Каждому городу нужно заранее поставить в соответствие нужный код (37 - Москва), узнать их можно здесь. Как работать с curl, можно узнать в этой статье на webdevkin-е

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


Пишем php-код для получения информации о погоде

Как брать данные, уже понятно, алгоритм прост: вытаскиваем из $_POST параметр city - выбранный город, формируем строку вида http://xml.meteoservice.ru/export/gismeteo/point/$cityId.xml. Это путь к файлу с погодой. Отправляем на него get-запрос curl-ом. Полученный ответ парсим и вытаскиваем из него нужные параметры: минимальная и максимальная температуры. Полученные числа отдаем в json-объекте обратно в браузер.

    $cityId = (int)$_POST['city'];
    
    $url = "http://xml.meteoservice.ru/export/gismeteo/point/$cityId.xml";
    
    $temperature = '';
    if ($curl = curl_init()) {
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($curl);
        curl_close($curl);
        
        $xml = simplexml_load_string($result);
        $object = $xml->REPORT->TOWN->FORECAST[0]->TEMPERATURE;
        $temperature = $object['min'] . '-' . $object['max'];
    }
    
    echo json_encode(array(
        'code' => 'success',
        'temperature' => $temperature
    ));

Поясню про simplexml_load_string - эта функция создает xml-объект, который мы уже можем парсить. А создается объект из строки xml, которую мы получаем от сервиса погоды.
REPORT->TOWN->FORECAST[0]->TEMPERATURE - это путь по узлам xml, чтобы добраться до нужных значений температуры. Пройдите по ссылке выше (московская погода) и наглядно увидите эти пути.
Если что-то непонятно, спрашивайте в комментариях.


Итого

И на этом все! Мы создали несложный виджет для получения информации о погоде. Главная его особенность в том, что его очень легко встроить на любой сайт, достаточно всего лишь вставить в html-код такие строки

	
	
	

Еще раз ссылки:
Демо страницы с виджетом
Архив с исходниками

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

Что еще почитать на похожую тему

Демо Исходники
Заходите в группу в контакте - https://vk.com/webdevkin
Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.
Как Вам статья? Оцените!