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

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

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

Предпосылка

В этой статье предполагается, что у читателя есть следующее:

  • Иметь базовые знания HTML/CSS и JavaScript
  • Иметь хорошее представление о NodeJS и о том, как работают SPA.
  • Можно настроить приложение узла, чтобы продолжить и выполнить пример.

Что такое виджет и зачем его использовать?

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

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

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

Как создать виджет с помощью JavaScript?

Продолжая первоначальную иллюстрацию в начале этой статьи — приложение Tega Photo session. Мы создадим виджет для Теги, чтобы его доступные сеансы могли отображаться для его клиентов; таким образом, они могут беспрепятственно бронировать сеансы с ним.

Шаг 1. Настройте сервер узла

Сначала создайте папку PhotoSessionWidget на локальном компьютере, войдите в папку, инициируйте проект узла с помощью npm и установите модуль экспресс-пакета, как показано ниже.

 mkdir PhotoSessionWidget
 cd PhotoSessionWidget 
 npm init -y
 npm install express

Следующее, что мы сделаем, — создадим файл сервера в папке PhotoSessionWidget. Я называю мой server.js. Вы можете создать его, как показано ниже:

touch server.js

Внутри server.js напишите в него приведенный ниже код.

 
const express = require("express");
const path = require("path");
 
const app = express();
 
app.use("/static", express.static(path.resolve(__dirname, "frontend", "static")));
 
app.get("/*", (req, res) => {
    res.sendFile(path.resolve(__dirname, "frontend", "index.html"));
});
 
app.listen(process.env.PORT || 3001, () => console.log("Server running..."));

Этот код настраивает сервер с помощью библиотеки Express. Он устанавливает статический каталог и сообщает серверу искать в папке «frontend» статические файлы при выполнении запроса. Он также указывает серверу обслуживать файл «index.html» при любом сделанном запросе, независимо от маршрута. Наконец, он сообщает серверу прослушивать порт 3001 или порт среды (если он установлен).

Шаг 2. Настройте внешний интерфейс

Создайте папку внешнего интерфейса внутри папки внешнего интерфейса, создайте статическую папку и файл index.html, используя команды ниже:

mkdir frontend
cd frontend
mkdir static
touch index.html

Ваша структура папок уже должна выглядеть так

Код index.html показан ниже.

 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Widget</title>
    <link rel="stylesheet" href="/static/css/index.css">
</head>
<body>
    <script type="module" src="static/js/widget/index.js"></script>
</body>
</html>

В SPA либо весь необходимый код — HTML, JavaScript и CSS — извлекается при загрузке одной страницы, либо соответствующие ресурсы динамически загружаются и добавляются на страницу по мере необходимости, обычно в ответ на действия пользователя. index.html — это та единственная страница, которая извлекает ресурсы, такие как index.js, заключенные в теге скрипта, и index.css, заключенные в теге ссылки.

Если вы внимательно наблюдаете за загрузкой этой страницы, будет запущен только скрипт. Это делается для того, чтобы при запуске нашего сервера мы могли получить прямой доступ к виджету на localhost:3000/.

Шаг 3. Написание компонента виджета

В папке static создайте CSS и JS папка. Папка CSS предназначена для управления файлами CSS. В этом случае у нас есть только один файл CSS, который был получен с помощью тега ссылки ранее в index.html. Мы не будем на этом останавливаться. Наше внимание сосредоточено на папке js, где мы можем создать папку виджета, а затем файл index.js, используя приведенные ниже команды.

mkdir css
mkdir js
cd js
mkdir widget
cd widget
touch index.js

Общая структура файла widget/index.js такова, что прослушиватели событий активируются при различных взаимодействиях с пользователем, как показано ниже. URL-адрес — это базовый URL-адрес, необходимый для получения данных сеанса.

const url = `
https://stoplight.io/mocks/pipeline/pipelinev2-projects/111233856
`;
 
const cardMain = document.createElement("div");
document.addEventListener("DOMContentLoaded", () => {
    
});

Во-первых, при загрузке контента ожидается реализация определенной логики.

1. Проверьте, не указан ли идентификатор продавца в качестве атрибута «id» загружаемого скрипта. Если он не указан, возьмите только что созданный элемент div (cardMain) и добавьте код HTML/CSS, предоставляющий пользователю инструкции о том, как добавьте идентификатор продавца. Затем добавьте div к телу.

let scripts = document.getElementsByTagName('script');
    let merchantId= scripts[0].getAttribute('id');
    if (merchantId === null) {
        cardMain.innerHTML+=`
        <div style="background:#fff; padding:2%;" >
            <h1>Sorry! you are seeing this page because you did not add your Merchant ID. See below for instructions</h1>
            <p>1. Add this Script to the body of your page</p>
            <div class="clipp"> 
                <span id="copy-script-text">&lt;script id="{MERCHANTID}" src="https://cut-session.onrender.com/static/js/widget/index.js"&gt;&lt;/script&gt;</span>
                <button class="clipp-btn" id="copy-script" data-copy>Copy</button>
            </div>
 
            <p>2. Add this css link to the head of the page</p>
            <div class="clipp"> 
                <span id="copy-css-text">&lt;link rel="stylesheet" href="https://cut-session.onrender.com/static/css/index.css"&gt;</span>   
                <button class="clipp-btn" id="copy-css" data-copy>Copy</button>
            </div>
            <p>Your Page should look like this when you are done</p>
            <div class="clipp"> 
                <span id="copy-html-text">
                    &lt;html&gt;
                    &lt;head&gt;
                    &lt;title&gt;Demo page of the widget&lt;/title&gt;
                    &lt;link rel="stylesheet" href="https://cut-session.onrender.com/static/css/index.css"&gt;
                        &lt;/head&gt;
                        &lt;body&gt;
                        &lt;script id="{MERCHANTID}" src="https://cut-session.onrender.com/static/js/widget/index.js"&gt;&lt;/script&gt;
                        &lt;/body&gt;
                    
                    &lt;/html&gt;
                </span>   
                <button class="clipp-btn" id="copy-html" data-copy>Copy</button>
            </div>
        </div>
        `;
        document.body.appendChild(cardMain);
    }

2. В противном случае продолжите получение данных и заполните созданный div результатами. Если результата нет, добавьте в div «Извините, пока нет данных!». Однако во время ожидания результата добавьте загрузчик в div и, когда результат будет получен, удалите загрузчик.

else{
        let options = {method: 'GET', headers: {'Content-Type': 'application/json', Prefer: 'code=200, dynamic=true'}};
        cardMain.innerHTML+=`<div class="loader"></div>`;
        document.body.appendChild(cardMain);
        fetch(`${url}/studios/${merchantId}`, options)
        .then(response => response.json())
        .then(response => {
            cardMain.style.width = "80%";
            cardMain.style.padding = "1% 0";
            cardMain.style.height = "40vh";
            cardMain.style.display = "grid";
            cardMain.style.gridTemplateColumns = "auto auto auto auto";
            cardMain.style.marginLeft = "auto";
            cardMain.style.marginRight = "auto";
            
            if (response.length > 0) { 
                cardMain.innerHTML="";
                response.forEach(x => {
                    cardMain.innerHTML+=`
                    <div class="cardy" id="${x.id}" data-merchantid>
                        <div class="innder-cardy" id="${x.id}" data-merchantid></div>
                        <h5 class="card-h5">Type: ${x.type}</h5>
                        <h5 class="card-h5">Period: ${x.startsAt.slice(0,-8)} - ${x.endsAt.slice(0,-8)}</h5>
                        <button type="button" class="btn-book" id="${x.id}"  data-bookings>Book this session</button>
                    </div>
                    `;
                });
                document.body.appendChild(cardMain);
            }
            else{
                cardMain.innerHTML+=`
                    <p>Sorry no data yet!</p>
                `
            }
        
            
        }).catch(err => console.error(err));
    }

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

Хотя приведенное выше зависит от прослушивателя событий «DOMContentLoaded», остальное зависит от прослушивателя событий «щелчок», как показано ниже.

const cardModal = document.createElement("div");
    document.body.addEventListener("click", e => {
    if (e.target.matches("[data-bookings]")) {
        // write code here
    }
    if (e.target.matches("[data-bookings-submit]")) { 
        // write code here
    }
    if (e.target.matches("[data-diff]")) {
        // write code here
    }
    if (e.target.matches("[data-copy]")) {
        // write code here
    }
 
});

3. При нажатии Запись сеанса отображается модальное окно (cardModal). Фрагмент кода показан ниже

document.body.addEventListener("click", e => {
        if (e.target.matches("[data-bookings]")) {
            e.preventDefault();
            cardModal.innerHTML+="";
            document.body.appendChild(cardModal);
            cardModal.style.width = "40%";
            cardModal.style.padding = "10% 30%";
            cardModal.style.height = "100vh";
            cardModal.style.backgroundColor="#00000040";
            cardModal.style.position = "fixed";
            cardModal.style.top = "0";
            cardModal.style.zIndex = 1000;
            cardModal.innerHTML+=`
                
                <form>
                    <p data-diff style="cursor:pointer">X</p>
                    <p id="message"></p>
                    <input class="form-input-widget" type="text" id="sessionId" name="sessionId" data-sessionid  minlength="15" maxlength="100" required hidden />
                    
                    <input class="form-input-widget" type="date" id="date" name="date" data-date  required />
                    <p id="no-warning"></p>
                    <input class="form-input-widget" type="text" id="title" name="title" data-title  minlength="2" maxlength="75" placeholder="Title" />
                    <p id="title-warning"></p>
                    <textarea class="form-input-widget" style="height:100px"  id="notes" name="notes" data-notes  minlength="2" maxlength="500" placeholder="Notes"></textarea>
                    <p id="notes-warning"></p>
 
                    <button class="form-btn" type="button" id="booking-sumbit-btn" data-bookings-submit>Submit</button>
 
                    <div class="bottom-signup"><p data-diff>Book different Session</p></div>
                </form>
                
            `
            
            document.body.appendChild(cardModal);
            document.getElementById('sessionId').value = e.target.id;
            
        }

Вид пользовательского интерфейса выглядит так

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

if (e.target.matches("[data-bookings-submit]")) {
            e.preventDefault();
            let btn = document.getElementById('booking-sumbit-btn')
            btn.disabled = true;
            btn.innerText = "Submitting Data..."
            let formData
            formData = {
                sessionId: document.getElementById("sessionId").value,
                userId: merchantId,
                date: document.getElementById("date").value,
                notes: document.getElementById("notes").value,
                title: document.getElementById("title").value,
 
            }
            const options = {
                method: 'POST',
                headers: {'Content-Type': 'application/json', Prefer: 'code=200, dynamic=true'},
                body: JSON.stringify(formData) 
              };
              
              fetch(`${url}/bookings`, options)
                .then(response => response.json())
                .then(response => {
                    document.getElementById("message").style.display = "none";
                    document.getElementById("message").innerText = "";
                    if (response.errors) {
                        document.getElementById("message").style.display = "block";
                        document.getElementById("message").style.color = "red";
                        document.getElementById("message").style.textAlign = "center"
                        document.getElementById("message").innerText = "Oops! an error occured";
                        btn.innerText = "Submit Again";
                        btn.disabled = false;
                    }
                    else{
                        document.getElementById("message").style.display = "block";
                        document.getElementById("message").style.color = "green";
                        document.getElementById("message").style.textAlign = "center"
                        document.getElementById("message").innerText = "Studio Session successfully booked!";
                        btn.innerText = "Completed!"
                        
                    }                .catch(err => console.error(err));
                    console.log(response)
                })
 
            
        }

Вид пользовательского интерфейса выглядит так

5. При нажатии кнопки «Закрыть» или «Забронировать другой сеанс» модальное окно удаляется. Фрагмент кода показан ниже.

if (e.target.matches("[data-diff]")) {
    e.preventDefault();
    cardModal.innerHTML="";
    document.body.appendChild(cardModal);
    cardModal.remove();
}

Шаг 4. Собираем все вместе

Полный фрагмент кода для файла widget/index.js показан ниже.

const url = `
https://stoplight.io/mocks/pipeline/pipelinev2-projects/111233856
`;
 
const cardMain = document.createElement("div");
document.addEventListener("DOMContentLoaded", () => {
    let scripts = document.getElementsByTagName('script');
    let merchantId= scripts[0].getAttribute('id');
    if (merchantId === null) {
        cardMain.innerHTML+=`
        <div style="background:#fff; padding:2%;" >
            <h1>Sorry! you are seeing this page because you did not add your Merchant ID. See below for instructions</h1>
            <p>1. Add this Script to the body of your page</p>
            <div class="clipp"> 
                <span id="copy-script-text">&lt;script id="{MERCHANTID}" src="https://cut-session.onrender.com/static/js/widget/index.js"&gt;&lt;/script&gt;</span>
                <button class="clipp-btn" id="copy-script" data-copy>Copy</button>
            </div>
 
            <p>2. Add this css link to the head of the page</p>
            <div class="clipp"> 
                <span id="copy-css-text">&lt;link rel="stylesheet" href="https://cut-session.onrender.com/static/css/index.css"&gt;</span>   
                <button class="clipp-btn" id="copy-css" data-copy>Copy</button>
            </div>
            <p>Your Page should look like this when you are done</p>
            <div class="clipp"> 
                <span id="copy-html-text">
                    &lt;html&gt;
                    &lt;head&gt;
                    &lt;title&gt;Demo page of the widget&lt;/title&gt;
                    &lt;link rel="stylesheet" href="https://cut-session.onrender.com/static/css/index.css"&gt;
                        &lt;/head&gt;
                        &lt;body&gt;
                        &lt;script id="{MERCHANTID}" src="https://cut-session.onrender.com/static/js/widget/index.js"&gt;&lt;/script&gt;
                        &lt;/body&gt;
                    
                    &lt;/html&gt;
                </span>   
                <button class="clipp-btn" id="copy-html" data-copy>Copy</button>
            </div>
        </div>
        `;
        document.body.appendChild(cardMain);
    }
    else{
        let options = {method: 'GET', headers: {'Content-Type': 'application/json', Prefer: 'code=200, dynamic=true'}};
        cardMain.innerHTML+=`<div class="loader"></div>`;
        document.body.appendChild(cardMain);
        fetch(`${url}/studios/${merchantId}`, options)
        .then(response => response.json())
        .then(response => {
            cardMain.style.width = "80%";
            cardMain.style.padding = "1% 0";
            cardMain.style.height = "40vh";
            cardMain.style.display = "grid";
            cardMain.style.gridTemplateColumns = "auto auto auto auto";
            cardMain.style.marginLeft = "auto";
            cardMain.style.marginRight = "auto";
            
            if (response.length > 0) { 
                cardMain.innerHTML="";
                response.forEach(x => {
                    cardMain.innerHTML+=`
                    <div class="cardy" id="${x.id}" data-merchantid>
                        <div class="innder-cardy" id="${x.id}" data-merchantid></div>
                        <h5 class="card-h5">Type: ${x.type}</h5>
                        <h5 class="card-h5">Period: ${x.startsAt.slice(0,-8)} - ${x.endsAt.slice(0,-8)}</h5>
                        <button type="button" class="btn-book" id="${x.id}"  data-bookings>Book this session</button>
                    </div>
                    `;
                });
                document.body.appendChild(cardMain);
            }
            else{
                cardMain.innerHTML+=`
                    <p>Sorry no data yet!</p>
                `
            }
        
            
        }).catch(err => console.error(err));
    }
    
 
      const cardModal = document.createElement("div");
      document.body.addEventListener("click", e => {
        if (e.target.matches("[data-bookings]")) {
            e.preventDefault();
            cardModal.innerHTML+="";
            document.body.appendChild(cardModal);
            cardModal.style.width = "40%";
            cardModal.style.padding = "10% 30%";
            cardModal.style.height = "100vh";
            cardModal.style.backgroundColor="#00000040";
            cardModal.style.position = "fixed";
            cardModal.style.top = "0";
            cardModal.style.zIndex = 1000;
            cardModal.innerHTML+=`
                
                <form>
                    <p data-diff style="cursor:pointer">X</p>
                    <p id="message"></p>
                    <input class="form-input-widget" type="text" id="sessionId" name="sessionId" data-sessionid  minlength="15" maxlength="100" required hidden />
                    
                    <input class="form-input-widget" type="date" id="date" name="date" data-date  required />
                    <p id="no-warning"></p>
                    <input class="form-input-widget" type="text" id="title" name="title" data-title  minlength="2" maxlength="75" placeholder="Title" />
                    <p id="title-warning"></p>
                    <textarea class="form-input-widget" style="height:100px"  id="notes" name="notes" data-notes  minlength="2" maxlength="500" placeholder="Notes"></textarea>
                    <p id="notes-warning"></p>
 
                    <button class="form-btn" type="button" id="booking-sumbit-btn" data-bookings-submit>Submit</button>
 
                    <div class="bottom-signup"><p data-diff>Book different Session</p></div>
                </form>
                
            `
            
            document.body.appendChild(cardModal);
            document.getElementById('sessionId').value = e.target.id;
            
        }
        if (e.target.matches("[data-bookings-submit]")) {
            e.preventDefault();
            let btn = document.getElementById('booking-sumbit-btn')
            btn.disabled = true;
            btn.innerText = "Submitting Data..."
            let formData
            formData = {
                sessionId: document.getElementById("sessionId").value,
                userId: merchantId,
                date: document.getElementById("date").value,
                notes: document.getElementById("notes").value,
                title: document.getElementById("title").value,
 
            }
            const options = {
                method: 'POST',
                headers: {'Content-Type': 'application/json', Prefer: 'code=200, dynamic=true'},
                body: JSON.stringify(formData) 
              };
              
              fetch(`${url}/bookings`, options)
                .then(response => response.json())
                .then(response => {
                    document.getElementById("message").style.display = "none";
                    document.getElementById("message").innerText = "";
                    if (response.errors) {
                        document.getElementById("message").style.display = "block";
                        document.getElementById("message").style.color = "red";
                        document.getElementById("message").style.textAlign = "center"
                        document.getElementById("message").innerText = "Oops! an error occured";
                        btn.innerText = "Submit Again";
                        btn.disabled = false;
                    }
                    else{
                        document.getElementById("message").style.display = "block";
                        document.getElementById("message").style.color = "green";
                        document.getElementById("message").style.textAlign = "center"
                        document.getElementById("message").innerText = "Studio Session successfully booked!";
                        btn.innerText = "Completed!"
                        
                    }
                    console.log(response)
                })
                .catch(err => console.error(err));
            
        }
        if (e.target.matches("[data-diff]")) {
            e.preventDefault();
            cardModal.innerHTML="";
            document.body.appendChild(cardModal);
            cardModal.remove();
        }
        if (e.target.matches("[data-copy]")) {
            if (e.target.id == "copy-script") {
                document.getElementById("copy-script").innerText = "Copied!"
                setTimeout(() => {
                    document.getElementById("copy-script").innerText = "Copy"  
                }, 1000);
                copytoClipboard("copy-script-text")
            }
            else if (e.target.id == "copy-css") {
                document.getElementById("copy-css").innerText = "Copied!"
                setTimeout(() => {
                    document.getElementById("copy-css").innerText = "Copy"  
                }, 1000);
                copytoClipboard("copy-css-text")
            }
            else if (e.target.id == "copy-html") {
                document.getElementById("copy-html").innerText = "Copied!"
                setTimeout(() => {
                    document.getElementById("copy-html").innerText = "Copy"  
                }, 1000);
                copytoClipboard("copy-css-text")
            }
            
        }
 
    });
});
 
const copytoClipboard = (id)=> {
    // Get the text field
    let copyText = document.getElementById(id);
     // Copy the text inside the text field
    navigator.clipboard.writeText(copyText.innerHTML);
  
    // Alert the copied text
    console.log("Copied the text: " + copyText.innerHTML);
}



Запустите свой сервер

node server.js

Если все пойдет по плану. если вы откроете http://localhost:3001/ на своей локальной машине, вы увидите это

Поэтому создайте файл HTML внутри папки виджета. Скопируйте и вставьте в него приведенный ниже фрагмент кода.

<html>
<head>
    <title>Demo page of the widget</title>
    <link rel="stylesheet" href="../../css/index.css">
</head>
<body>
    <!-- id provided -->
    <script id="b2c13957-1b5a-5069-3ae5-713ec739fdd0" src="index.js"></script> 
    
</body>
 
</html>

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

В случае, если скрипт не имеет идентификатора, как показано во фрагменте кода ниже

<html>
<head>
    <title>Demo page of the widget</title>
    <link rel="stylesheet" href="../../css/index.css">
</head>
<body>
    <!-- id NOT provided -->
    <script  src="index.js"></script>
</body>
 
</html>

Откройте на своей локальной машине. ты должен увидеть это

Вы можете приступить к развертыванию, а затем заменить скрипт, как показано ниже.

<html>
<head>
    <title>Demo page of the widget</title>
    <link 
        rel="stylesheet" 
        href="https://cut-session.onrender.com/static/css/index.css">
</head>
<body>
    <script 
        id=ca158ab6-9c46-2133-15b0-e0c76982dc8c 
        src="https://cut-session.onrender.com/static/js/widget/index.js">
    </script>
    
</body>
 
</html>

Заключение

В заключение, виджеты JavaScript являются важной частью дизайна и разработки веб-сайтов. Они позволяют взаимодействовать с посетителями веб-сайта, создавать динамический контент и отслеживать эффективность веб-сайта. Как видите, Тега может с легкостью обслуживать больше клиентов и лучше планировать сеансы в зависимости от его доступности.