با استفاده از Python و JavaScript یک ویجت چت بسازید

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 30 مرداد 1397
دسته بندی ها : پایتون

ساخت محصولات دیجیال با کیفیت، یک مسئله ضروری برای دستیابی به مشتریان طولانی مدت، و ارتباطات ناکافی روش موثری برای از دست دادن آن‌ها است. در حال حاضر اینترنت بزرگترین بازار دنیا است و هر کسی در حال ساخت چیزی برای مخاطبان آنلاین است. گرچه، این که راهی برای دریافت نظرات کاربران و تعامل با آن‌ها به صورت Realtime وجود نداشته باشد، یک نکته منفی به حساب می‌آید.

در این آموزش، به نحوه ساخت یک ویجت چت realtime با استفاده از Pusher، Python و JavaScript نگاهی خواهیم داشت. پس از این که ساخت برنامه تمام شد، نتیجه نهایی چنین ظاهری داشته، و به این صورت کار خواهد کرد:

در تصویر بالا، می‌توانیم یک محصول دیجیتال به نام SPIN ببینیم که یک گزینه ویجت چت برای مشتریان دارد تا مشتریان با آن در تعامل باشند. در صفحه سمت چپ، یک مشتری این صفحه را مشاهده می‌کند و قبل از تایید فرم، اطلاعات خود را وارد می‌کند.

در صفحه سمت راست یک مدیر وجود دارد که می‌تواند تمام مشتریان متصل را ببیند و به تمام پیام‌های آن‌ها پاسخ داده، و یک پشتیبانی موثر و طولانی مدت را فراهم کند.

جدول محتویات:

  1. پیش‌نیاز‌ها
  2. راه‌اندازی محیط برنامه
  3. راه‌اندازی Pusher
  4. ساختار فایل و پوشه
  5. ساخت Backend
  6. ساخت Frontend
  7. اجرای برنامه
  8. نتیجه گیری

پیش‌نیاز‌ها

برای پیش‌روی با این آموزش، نیازمند دانش پایه‌ای در زمینه‌های Python، Flask، JavaScript (سینتکس ES6) و jQuery هستید. همچنین باید این سه مورد را بر روی سیستم خود نصب داشته باشید:

  1. Python (نسخه 3.x و بالاتر)
  2. Virtualenv
  3. Flask

Virtualenv برای ساخت محیط‌های Python مجزا عالی است؛ پس می‌توانیم Dependencyها را در محیط‌های مجزا نصب کنیم، و شاخه پکیج‌های global خود را شلوغ نکنیم.

بیایید virtualenv را با این دستور نصب کنیم

 pip install virtualenv

نکته:  virtualenvبه طور پیشفرض بر روی Python 3 نصب است؛ پس اگر از این نسخه استفاده می‌کنید، نیازی به نصب آن نیست.

راه‌اندازی محیط برنامه

بیایید پوشه پروژه خود را بسازیم و یک محیط مجازی در آن راه‌اندازی کنیم:

mkdir python-pusher-chat-widget
cd python-pusher-chat-widget
virtualenv .venv
source .venv/bin/activate # Linux based systems
\path\to\env\Scripts\activate # Windows users

حال که محیط مجازی را راه‌اندازی کرده‌ایم، می‌توانیم Flask و باقی Dependencyها را با استفاده از این دستور نصب کنیم:

$ pip install flask flask-cors simplejson

از آنجایی که به کتابخانه Pusher برای بروزرسانی‌های Realtime نیاز خواهیم داشت، باید آن را نصب کنیم.

راه‌اندازی Pusher

در اینجا اولین قدم، دریافت برنامه Pusher Channels خواهد بود. برای این که برنامه پیام‌رسان realtime ما کار کند، باید مدارک برنامه را فراهم کنیم.

به وبسایت Pusher بروید و یک حساب کاربری بسازید. پس از ساخت حساب، باید یک برنامه جدید بسازید. مراحل ساخت برنامه را دنبال کنید، و مدارک برنامه خود را دریافت کنید. بعدا از آن‌ها استفاده خواهیم کرد.

یک کار دیگر نیز وجود دارد که باید در داشبورد انجام دهیم؛ زیرا مستقیما رویدادهای پیام را در سمت کاربر برنامه فعال خواهیم کرد، و باید یک ویژگی خاص را که به طور پیشفرض به دلایل امنیتی خاموش است، روشن کنیم.

در صفحه داشبورد، بر روی App setting کلیک کنید و به پایین صفحه بروید، و بعد گزینه‌ Enable client events را انتخاب کنید:

حال بیایید کتابخانه Pusher Python را نصب کنیم، تا بتوانیم از Pusher در برنامه خود استفاده کنیم:

$ pip install pusher

ساختار فایل و پوشه

در اینجا، ساختار فایل و پوشه برنامه را می‌بینید

├── python-pusher-chat-widget
           ├── app.py
           ├── static
           └── templates

پوشه static، فایل‌های static را شامل خوهد شد، تا همانطور که در استانداردهای Flask تعریف شده‌ است، استفاده شوند. پوشه templates، الگوهای HTML را نگه خواهد داشت. در برنامه ما، app.py نقطه ورود اصلی است و کد سمت سرور را در خود خواهد داشت.

بیایید فایل app.py و سپس پوشه‌های static و templates را بسازیم.

ساخت Backend

قبل از شروع به نوشتن کد برای تعیین نحوه رندر شدن frontend‌ برنامه، بیایید ابتدا backend و تمام endpointهایش را به کلی توسعه دهیم تا وقتی که frontend را ساختیم، چیزی برای برقراری ارتباط داشته باشد.

فایل app.py را باز کرده و کد زیر را در آن قرار دهید:

    // File: ./app.py

    from flask import Flask, render_template, request, jsonify, make_response, json

    from flask_cors import CORS

    from pusher import pusher

    import simplejson

    app = Flask(__name__)

    cors = CORS(app)

    app.config['CORS_HEADERS'] = 'Content-Type'

    # پیکربندی آبچکت پوشر

    pusher = pusher.Pusher(

    app_id='PUSHER_APP_ID',

    key='PUSHER_APP_KEY',

    secret='PUSHER_APP_SECRET',

    cluster='PUSHER_APP_CLUSTER',

    ssl=True)

    @app.route('/')

    def index():

        return render_template('index.html')

    @app.route('/admin')

    def admin():

        return render_template('admin.html')

    @app.route('/new/guest', methods=['POST'])

    def guestUser():

        data = request.json

        pusher.trigger(u'general-channel', u'new-guest-details', { 

            'name' : data['name'], 

            'email' : data['email']

            })

        return json.dumps(data)

    @app.route("/pusher/auth", methods=['POST'])

    def pusher_authentication():

        auth = pusher.authenticate(channel=request.form['channel_name'],socket_id=request.form['socket_id'])

        return json.dumps(auth)

    if __name__ == '__main__':

        app.run(host='0.0.0.0', port=5000, debug=True)

نکته: کلیدهای PUSHER_APP_* را با مقدایر داشبورد Pusher خود جایگزین کنید. 

منطق این برنامه ساده است. ما به یک کانال عمومی Pusher نیاز داریم تا هر زمان که کاربر جدیدی به ویجت چت متصل می‌شود، جزئیاتش (از طریق یک کانال عمومی) به مدیر ارسال شود و مدیر بتواند او را با استفاده از ایمیل او به عنوان یک ID خاص، به یک کانال شخصی وصل کند. مدیر و کاربر بعدا می‌توانند از طریق کانال خصوصی، بیشتر با هم در ارتباط باشند.

بیایید به فایل app.py‌ برویم و ببینیم که چگونه به این منطق رسیدگی می‌کند. در ابتدا تمام پکیج‌های مورد نیاز را وارد کرده، و سپس یک نمونه Pusher جدید را ثبت کردیم. بعد از آن، چهار endpoint را تعریف کردیم:

  • / - این endpoint الگوی HTML ثابت که صفحه اصلی برنامه را تعریف می‌کند، بر می‌گرداند.
  • /admin - این endpoint الگوی HTML ثابت که داشبورد مدیر را تعریف می‌کند، بر می‌گرداند.
  • /new/guest/ - این endpoint یک درخواست POST شامل جزئیات کاربر جدید را دریافت می‌کند و آن را در رویداد new-guest-channel به کانال عمومی می‌فرستد. مدیر موجود در سمت دیگر، با اتصال به یک کانال عمومی با استفاده از ایمیل کاربر، به این رویداد پاسخ می‌دهد.

ما از متد راه‌اندازی بر روی نمونه Pusher استفاده کردیم. متد راه‌اندازی، این سینتکس را دارد: pusher.trigger(“a_channel”, “an_event”, {key: “data”}).

  • /pusher/auth - این endpoint مسئول قادرسازی برنامه ما برای اتصال به یک کانال خصوصی است. بدون این endpoint، اجازه نخواهیم داشت که رویدادهای کاربر را از طریق کانال‌های خصوصی ارسال کنیم.

ساخت Frontend

در این بخش، ما این کارها را انجام خواهیم داد:

  • ساخت دو فایل جدید به نام‌های index.html و admin.html در شاخه templates.
  • ساخت شاخه‌ای به نام img در شاخه static و اضافه کردن تصویر پس‌زمینه که bg.jpg نام دارد، به آن.
  • ساخت شاخه‌هایی با نام‌های css و js در شاخه static. در شاخه css، فایل‌های جدیدی به نام‌های admin.css و app.css می‌سازیم. سپس در شاخه js، فایل‌های جدیدی به نام‌های admin.js و app.js می‌سازیم.

ما از Bootstrap به عنوان یک استایل پایه برای برنامه خود استفاده خواهیم کرد. همچنین از کتابخانه‌های دیگر استفاده خواهیم کرد، پس بیایید منابع را دریافت کنیم و آن‌ها را در شاخه مناسب موجود در شاخه static قرار دهیم.

این فایل‌ها را در شاخه static/js قرار دهید:

  1. axios.js - می‌توانید سورس کد آن را از اینجا دانلود کنید.
  2. bootstrap.js - می‌توانید سورس کد آن را از اینجا دانلود کنید.
  3. jquery.js - می‌توانید سورس کد آن را از اینجا دانلود کنید.
  4. popper.js - می‌توانید سورس کد آن را از اینجا دانلود کنید.

این فایل را در شاخه static/css قرار دهید:

boostrap.css - می‌توانید سورس کد آن را از اینجا دانلود کنید.

ساختار پوشه جدید باید به این صورت باشد:

├── python-pusher-chat-widget
      ├── app.py
      ├── static
        ├── css
          ├── admin.css
          ├── app.css
          ├── bootstrap.css
        ├── img
          ├── bg.jpg
        ├── js
          ├── admin.js
          ├── app.js
          ├── axios.js
          ├── bootstrap.js
          ├── jquery.js
          ├── popper.js
      ├── templates
        ├── admin.html
        ├── index.html

اگر حال این ساختار پوشه را دارید، آماده به کار هستید.

راه‌اندازی view صفحه اصلی

در فایل templates/index.html، این کد را قرار دهید:

<!-- File: ./templates/index.html -->

    <!doctype html>

    <html lang="en">

      <head>

        <meta charset="utf-8">

        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <title>Spin Spinner Spinnest!</title>

        <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.css') }}">

        <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">

      </head>

      <body>

        <div class="site-wrapper">

          <div class="site-wrapper-inner">

            <div class="cover-container">

              <header class="masthead clearfix">

                <div class="inner">

                  <h3 class="masthead-brand">SPIN</h3>

                  <nav class="nav nav-masthead">

                    <a class="nav-link active" href="#">Home</a>

                    <a class="nav-link" href="#">Features</a>

                    <a class="nav-link" href="#">Contact</a>

                  </nav>

                </div>

              </header>

              <main role="main" class="inner cover">

                <h1 class="cover-heading">SPIN</h1>

                <p class="lead">SPIN is a simple realtime chat widget powered by Pusher.</p>

                <p class="lead">

                  <a href="#" class="btn btn-lg btn-secondary">GO for a SPIN?</a>

                </p>

              </main>

              <footer class="mastfoot">

              </footer>

            </div>

          </div>

        </div>

        <div class="chatbubble">

            <div class="unexpanded">

                <div class="title">Chat with Support</div>

            </div>

            <div class="expanded chat-window">

              <div class="login-screen container">

                <form id="loginScreenForm">

                  <div class="form-group">

                    <input type="text" class="form-control" id="fullname" placeholder="Name*" required>

                  </div>

                  <div class="form-group">

                    <input type="email" class="form-control" id="email" placeholder="Email Address*" required>

                  </div>

                  <button type="submit" class="btn btn-block btn-primary">Start Chat</button>

                </form>

              </div>

              <div class="chats">

                <div class="loader-wrapper">

                  <div class="loader">

                    <span>{</span><span>}</span>

                  </div>

                </div>

                <ul class="messages clearfix">

                </ul>

                <div class="input">

                  <form class="form-inline" id="messageSupport">

                    <div class="form-group">

                      <input type="text" autocomplete="off" class="form-control" id="newMessage" placeholder="Enter Message">

                    </div>

                    <button type="submit" class="btn btn-primary">Send</button>

                  </form>

                </div>

              </div>

            </div>

        </div>    

        <script src="https://js.pusher.com/4.0/pusher.min.js"></script>

        <script src="{{ url_for('static', filename='js/jquery.js') }}"></script>

        <script src="{{ url_for('static', filename='js/popper.js') }}"></script>

        <script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>

        <script src="{{ url_for('static', filename='js/axios.js') }}"></script>

        <script src="{{ url_for('static', filename='js/app.js') }}"></script>

      </body>

    </html>

در این فایل، HTML مربوط به صفحه اصلی را داریم. همچنین از تابع url_for در Flask استفاده کردیم تا به طور دینامیک به تمام اسکریپت‌های محلی و استایل‌هایی که ساختیم لینک شویم.

از آنجایی که ما از برنامه خود می‌خواهیم که پیام‌ها را به صورت realtime ارسال کرده و دریافت کند، باید کتابخانه JavaScript رسمی Pusher را با استفاده از این کد وارد کنیم:

<script src="https://js.pusher.com/4.0/pusher.min.js"></script>

ما برخی کلاس‌های سفارشی را در عناصر HTML شامل کردیم. گرچه، اگر این کلاس‌ها را در فایل CSS مربوطه تعریف نکنیم، بی استفاده خواهند بود. فایل static/css/app.css را باز کرده، و این کد را در آن قرار دهید:

    /* File: static/css/app.css */

    a,

    a:focus,

    a:hover {

      color: #fff;

    }

    .btn-secondary,

    .btn-secondary:hover,

    .btn-secondary:focus {

      color: #333;

      text-shadow: none;

      background-color: #fff;

      border: .05rem solid #fff;

    }

    html,

    body {

      height: 100%;

      background-color: #333;

    }

    body {

      color: #fff;

      text-align: center;

      text-shadow: 0 .05rem .1rem rgba(0,0,0,.5);

    }

    .site-wrapper {

      display: table;

      width: 100%;

      height: 100%; /* For at least Firefox */

      min-height: 100%;

      box-shadow: inset 0 0 5rem rgba(0,0,0,.5);

      background: url(../img/bg.jpg);

      background-size: cover;

      background-repeat: no-repeat;

      background-position: center;

    }

    .site-wrapper-inner {

      display: table-cell;

      vertical-align: top;

    }

    .cover-container {

      margin-right: auto;

      margin-left: auto;

    }

    .inner {

      padding: 2rem;

    }

    .masthead {

      margin-bottom: 2rem;

    }

    .masthead-brand {

      margin-bottom: 0;

    }

    .nav-masthead .nav-link {

      padding: .25rem 0;

      font-weight: 700;

      color: rgba(255,255,255,.5);

      background-color: transparent;

      border-bottom: .25rem solid transparent;

    }

    .nav-masthead .nav-link:hover,

    .nav-masthead .nav-link:focus {

      border-bottom-color: rgba(255,255,255,.25);

    }

    .nav-masthead .nav-link + .nav-link {

      margin-left: 1rem;

    }

    .nav-masthead .active {

      color: #fff;

      border-bottom-color: #fff;

    }

    @media (min-width: 48em) {

      .masthead-brand {

        float: left;

      }

      .nav-masthead {

        float: right;

      }

    }

    .cover {

      padding: 0 1.5rem;

    }

    .cover .btn-lg {

      padding: .75rem 1.25rem;

      font-weight: 700;

    }

    .mastfoot {

      color: rgba(255,255,255,.5);

    }

    @media (min-width: 40em) {

      .masthead {

        position: fixed;

        top: 0;

      }

      .mastfoot {

        position: fixed;

        bottom: 0;

      }

      .site-wrapper-inner {

        vertical-align: middle;

      }

      .masthead,

      .mastfoot,

      .cover-container {

        width: 100%;

      }

    }

    @media (min-width: 62em) {

      .masthead,

      .mastfoot,

      .cover-container {

        width: 42rem;

      }

    }

    .chatbubble {

        position: fixed;

        bottom: 0;

        right: 30px;

        transform: translateY(300px);

        transition: transform .3s ease-in-out;

    }

    .chatbubble.opened {

        transform: translateY(0)

    }

    .chatbubble .unexpanded {

        display: block;

        background-color: #e23e3e;

        padding: 10px 15px 10px;

        position: relative;

        cursor: pointer;

        width: 350px;

        border-radius: 10px 10px 0 0;

    }

    .chatbubble .expanded {

        height: 300px;

        width: 350px;

        background-color: #fff;

        text-align: left;

        padding: 10px;

        color: #333;

        text-shadow: none;

        font-size: 14px;

    }

    .chatbubble .chat-window {

      overflow: auto;

    }

    .chatbubble .loader-wrapper {

        margin-top: 50px;

        text-align: center;

    }

    .chatbubble .messages {

        display: none;

        list-style: none;

        margin: 0 0 50px;

        padding: 0;

    }

    .chatbubble .messages li {

        width: 85%;

        float: left;

        padding: 10px;

        border-radius: 5px 5px 5px 0;

        font-size: 14px;

        background: #c9f1e6;

        margin-bottom: 10px;

    }

    .chatbubble .messages li .sender {

        font-weight: 600;

    }

    .chatbubble .messages li.support {

        float: right;

        text-align: right;

        color: #fff;

        background-color: #e33d3d;

        border-radius: 5px 5px 0 5px;

    }

    .chatbubble .chats .input {

        position: absolute;

        bottom: 0;

        padding: 10px;

        left: 0;

        width: 100%;

        background: #f0f0f0;

        display: none;

    }

    .chatbubble .chats .input .form-group {

        width: 80%;

    }

    .chatbubble .chats .input input {

        width: 100%;

    }

    .chatbubble .chats .input button {

        width: 20%;

    }

    .chatbubble .chats {

      display: none;

    }

    .chatbubble .login-screen {

      margin-top: 20px;

      display: none;

    }

    .chatbubble .chats.active,

    .chatbubble .login-screen.active {

      display: block;

    }

    /* Loader Credit: https://codepen.io/ashmind/pen/zqaqpB */

    .chatbubble .loader {

      color: #e23e3e;

      font-family: Consolas, Menlo, Monaco, monospace;

      font-weight: bold;

      font-size: 10vh;

      opacity: 0.8;

    }

    .chatbubble .loader span {

      display: inline-block;

      -webkit-animation: pulse 0.4s alternate infinite ease-in-out;

              animation: pulse 0.4s alternate infinite ease-in-out;

    }

    .chatbubble .loader span:nth-child(odd) {

      -webkit-animation-delay: 0.4s;

              animation-delay: 0.4s;

    }

    @-webkit-keyframes pulse {

      to {

        -webkit-transform: scale(0.8);

                transform: scale(0.8);

        opacity: 0.5;

      }

    }

    @keyframes pulse {

      to {

        -webkit-transform: scale(0.8);

                transform: scale(0.8);

        opacity: 0.5;

      }

    }

راه‌اندازی view داشبورد مدیر

این کد را در فایل templates/admin.html قرار دهید:

    <!-- File: templates/admin.html -->

    <!doctype html>

    <html lang="en">

      <head>

        <meta charset="utf-8">

        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

        <title>Admin</title>

        <link href="{{ url_for('static', filename='css/bootstrap.css') }}" rel="stylesheet">

        <link href="{{ url_for('static', filename='css/admin.css') }}" rel="stylesheet">

      </head>

      <body>

        <header>

            <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">

                <a class="navbar-brand" href="#">Dashboard</a>

            </nav>

        </header>

        <div class="container-fluid">

            <div class="row" id="mainrow">

                <nav class="col-sm-3 col-md-2 d-none d-sm-block bg-light sidebar">

                    <ul class="nav nav-pills flex-column" id="rooms">

                    </ul>

                </nav>

                <main role="main" class="col-sm-9 ml-sm-auto col-md-10 pt-3" id="main">

                    <h1>Chats</h1>

                    <p>? Select a chat to load the messages</p>

                    <p> </p>

                    <div class="chat" style="margin-bottom:150px">

                        <h5 id="room-title"></h5>

                        <p> </p>

                        <div class="response">

                            <form id="replyMessage">

                                <div class="form-group">

                                    <input type="text" placeholder="Enter Message" class="form-control" name="message" />

                                </div>

                            </form>

                        </div>

                        <div class="table-responsive">

                          <table class="table table-striped">

                            <tbody id="chat-msgs">

                            </tbody>

                        </table>

                    </div>

                </main>

            </div>

        </div>

        <script src="https://js.pusher.com/4.0/pusher.min.js"></script>

        <script src="{{ url_for('static', filename='js/jquery.js') }}"></script>

        <script src="{{ url_for('static', filename='js/popper.js') }}"></script>

        <script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>

        <script src="{{ url_for('static', filename='js/axios.js') }}"></script>

        <script src="{{ url_for('static', filename='js/admin.js') }}"></script>

      </body>

    </html>

فایل static/css/admin.css را باز کنید و این کد را در آن قرار دهید:

/* File: static/css/admin.css */

    body {

        padding-top: 3.5rem;

    }

    h1 {

        padding-bottom: 9px;

        margin-bottom: 20px;

        border-bottom: 1px solid #eee;

    }

    .sidebar {

        position: fixed;

        top: 51px;

        bottom: 0;

        left: 0;

        z-index: 1000;

        padding: 20px 0;

        overflow-x: hidden;

        overflow-y: auto;

        border-right: 1px solid #eee;

    }

    .sidebar .nav {

        margin-bottom: 20px;

    }

    .sidebar .nav-item {

        width: 100%;

    }

    .sidebar .nav-item + .nav-item {

        margin-left: 0;

    }

    .sidebar .nav-link {

        border-radius: 0;

    }

    .placeholders {

        padding-bottom: 3rem;

    }

    .placeholder img {

        padding-top: 1.5rem;

        padding-bottom: 1.5rem;

    }

    tr .sender {

        font-size: 12px;

        font-weight: 600;

    }

    tr .sender span {

        color: #676767;

    }

    .response {

        display: none;

    }

نوشتن اسکریپت app.js

در این بخش، اسکریپتی را خواهیم نوشت که با صفحه اصلی کار می‌کند و توابع کاربر را پشتیبانی می‌کند. این اسکریپت منطق برنامه را تعریف خواهد کرد و کاربر را قادر خواهد ساخت تا پس از وارد کردن جزئیات خود، فرم را تایید کند.

ما برخی توابع کمکی را داخل یک IIFE (بیانیه تابع سریعا اجرا شده = Immediately Invoked Function Expression) تعریف خواهیم کرد، که این توابع در هنگام بروز چند رویداد DOM اجرا خواهند شد و به توابع کمکی دیگر منتقل خواهند شد.

فایل app.js را باز کرده، و این کد را در آن قرار دهید:

    // File: static/js/app.js

    (function() {

        'use strict';

        var pusher = new Pusher('PUSHER_APP_KEY', {

            authEndpoint: '/pusher/auth',

            cluster: 'PUSHER_APP_CLUSTER',

            encrypted: true

        });

        // ----------------------------------------------------

        // جزئیات چت

        // ----------------------------------------------------

        let chat = {

            name:  undefined,

            email: undefined,

            myChannel: undefined,

        }

        // ----------------------------------------------------

        // عناصر مورد هدف

        // ----------------------------------------------------

        const chatPage   = $(document)

        const chatWindow = $('.chatbubble')

        const chatHeader = chatWindow.find('.unexpanded')

        const chatBody   = chatWindow.find('.chat-window')

        // ----------------------------------------------------

        // کمک‌ کننده‌های ثبت

        // ----------------------------------------------------

        let helpers = {

            // ----------------------------------------------------

            // نمایش صفحه چت

            // ----------------------------------------------------

            ToggleChatWindow: function () {

                chatWindow.toggleClass('opened')

                chatHeader.find('.title').text(

                    chatWindow.hasClass('opened') ? 'Minimize Chat Window' : 'Chat with Support'

                )

            },

            // --------------------------------------------------------------------

            // نمایش صفحه مناسب بین صفحات چت و ورود

            // --------------------------------------------------------------------

            ShowAppropriateChatDisplay: function () {

                (chat.name) ? helpers.ShowChatRoomDisplay() : helpers.ShowChatInitiationDisplay()

            },

            // ----------------------------------------------------

            // نمایش فرم ورود جزئیات

            // ----------------------------------------------------

            ShowChatInitiationDisplay: function () {

                chatBody.find('.chats').removeClass('active')

                chatBody.find('.login-screen').addClass('active')

            },

            // ----------------------------------------------------

            // نمایش صفحه پیام‌های چت روم

            // ----------------------------------------------------

            ShowChatRoomDisplay: function () {

                chatBody.find('.chats').addClass('active')

                chatBody.find('.login-screen').removeClass('active')

                setTimeout(function(){

                    chatBody.find('.loader-wrapper').hide()

                    chatBody.find('.input, .messages').show()

                }, 2000)

            },

            // ----------------------------------------------------

            // اتصال یک پیام به رابط کاربری پیام‌ها

            // ----------------------------------------------------

            NewChatMessage: function (message) {

                if (message !== undefined) {

                    const messageClass = message.sender !== chat.email ? 'support' : 'user'

                    chatBody.find('ul.messages').append(

                        `<li class="clearfix message ${messageClass}">

                            <div class="sender">${message.name}</div>

                            <div class="message">${message.text}</div>

                        </li>`

                    )

                    chatBody.scrollTop(chatBody[0].scrollHeight)

                }

            },

            // ----------------------------------------------------

            // ارسال یک پیام به کانال چت

            // ----------------------------------------------------

            SendMessageToSupport: function (evt) {

                evt.preventDefault()

                let createdAt = new Date()

                createdAt = createdAt.toLocaleString()

                const message = $('#newMessage').val().trim()

                chat.myChannel.trigger('client-guest-new-message', {

                    'sender': chat.name,

                    'email': chat.email,

                    'text': message,

                    'createdAt': createdAt 

                });

                helpers.NewChatMessage({

                    'text': message,

                    'name': chat.name,

                    'sender': chat.email

                })

                console.log("Message added!")

                $('#newMessage').val('')

            },

            // ----------------------------------------------------

            // وارد کردن کاربر به یک چت

            // ----------------------------------------------------

            LogIntoChatSession: function (evt) {

                const name  = $('#fullname').val().trim()

                const email = $('#email').val().trim().toLowerCase()

                // Disable the form

                chatBody.find('#loginScreenForm input, #loginScreenForm button').attr('disabled', true)

                if ((name !== '' && name.length >= 3) && (email !== '' && email.length >= 5)) {

                    axios.post('/new/guest', {name, email}).then(response => {

                        chat.name = name

                        chat.email = email

                        chat.myChannel = pusher.subscribe('private-' + response.data.email);

                        helpers.ShowAppropriateChatDisplay()

                    })

                } else {

                    alert('Enter a valid name and email.')

                }

                evt.preventDefault()

            }

        }

        // ------------------------------------------------------------------

        // انتظار برای یک رویداد پیام از مدیر

        // ------------------------------------------------------------------

        pusher.bind('client-support-new-message', function(data){

            helpers.NewChatMessage(data)

        })

        // ----------------------------------------------------

        // ثبت رویدادهای صفحه

        // ----------------------------------------------------

        chatPage.ready(helpers.ShowAppropriateChatDisplay)

        chatHeader.on('click', helpers.ToggleChatWindow)

        chatBody.find('#loginScreenForm').on('submit', helpers.LogIntoChatSession)

        chatBody.find('#messageSupport').on('submit', helpers.SendMessageToSupport)

    }())

در کد بالا، ما جاوااسکریپتی را داریم که نیروی اصلی ویجت‌های چت است. در این کد، ما با معرفی کردن Pusher شروع می‌کنیم. (کلید‌های PUSHER_* در داشبورد Pusher خود را به یاد داشته باشید)

ما یک ویژگی به نام helpers داریم که برخی توابع به آن متصل هستند. هر تابع، کامنتی دارد که کار آن درست قبل از تعریف شدن را توضیح می‌دهد. در پایین اسکریپت، جایی است که تمام رویدادها و listenerها را ثبت می‌کنیم.

نوشتن اسکریپت admin.js: کد موجود در فایل admin.js مشابه به app.js است و توابع کاملا مشابه هستند. فایل admin.js را باز کرده، و این کد را در آن قرار دهید:

    // File: static/js/admin.js

    (function () {

        'use strict';

        // ----------------------------------------------------

        // پیکربندی نمونه پوشر

        // ----------------------------------------------------

        var pusher = new Pusher('PUSHER_APP_KEY', {

            authEndpoint: '/pusher/auth',

            cluster: 'PUSHER_APP_CLUSTER',

            encrypted: true

          });

        // ----------------------------------------------------

        // جزئیات چت

        // ----------------------------------------------------

        let chat = {

            messages: [],

            currentRoom: '',

            currentChannel: '',

            subscribedChannels: [],

            subscribedUsers: []

        }

        // ----------------------------------------------------

        // اتصال به generalChannel

        // ----------------------------------------------------

        var generalChannel = pusher.subscribe('general-channel');

        // ----------------------------------------------------

        // عناصر مورد هدف

        // ----------------------------------------------------

        const chatBody = $(document)

        const chatRoomsList = $('#rooms')

        const chatReplyMessage = $('#replyMessage')

        // ----------------------------------------------------

        // ثبت کمک کننده‌ها

        // ----------------------------------------------------

        const helpers = {

            // ------------------------------------------------------------------

            // پاکسازی رابط کاربری پیام‌ها

            // ------------------------------------------------------------------

            clearChatMessages: () => $('#chat-msgs').html(''),

            // ------------------------------------------------------------------

            // اضافه کردن یک پیام جدید به صفحه چت

            // ------------------------------------------------------------------

            displayChatMessage: (message) => {

                if (message.email === chat.currentRoom) {

                    $('#chat-msgs').prepend(

                        `<tr>

                            <td>

                                <div class="sender">${message.sender} @ <span class="date">${message.createdAt}</span></div>

                                <div class="message">${message.text}</div>

                            </td>

                        </tr>`

                    )

                }

            },

            // ------------------------------------------------------------------

            // انتخاب یک چت‌روم جدید

            // ------------------------------------------------------------------

            loadChatRoom: evt => {

                chat.currentRoom = evt.target.dataset.roomId

                chat.currentChannel = evt.target.dataset.channelId

                if (chat.currentRoom !== undefined) {

                    $('.response').show()

                    $('#room-title').text(evt.target.dataset.roomId)

                }

                evt.preventDefault()

                helpers.clearChatMessages()

            },

            // ------------------------------------------------------------------

            // پاسخ به یک پیام

            // ------------------------------------------------------------------

            replyMessage: evt => {

                evt.preventDefault()

                let createdAt = new Date()

                createdAt = createdAt.toLocaleString()

                const message = $('#replyMessage input').val().trim()

                chat.subscribedChannels[chat.currentChannel].trigger('client-support-new-message', {

                    'name': 'Admin',

                    'email': chat.currentRoom,

                    'text': message, 

                    'createdAt': createdAt 

                });

                helpers.displayChatMessage({

                    'email': chat.currentRoom,

                    'sender': 'Support',

                    'text': message, 

                    'createdAt': createdAt

                })

                $('#replyMessage input').val('')

            },

        }

          // ------------------------------------------------------------------

          // انتظار برای رویدادی که جزئیات یک کاربر جدید را بر می‌گرداند

          // ------------------------------------------------------------------

          generalChannel.bind('new-guest-details', function(data) {

            chat.subscribedChannels.push(pusher.subscribe('private-' + data.email));

            chat.subscribedUsers.push(data);

            // رندر کردن لیست کاربران و پاکسازی لیست قبلی

            $('#rooms').html("");

            chat.subscribedUsers.forEach(function (user, index) {

                    $('#rooms').append(

                        `<li class="nav-item"><a data-room-id="${user.email}" data-channel-id="${index}" class="nav-link" href="#">${user.name}</a></li>`

                    )

            })

          })

          // ------------------------------------------------------------------

          // انتظار برای یک رویداد پیام جدید از یک مهمان

          // ------------------------------------------------------------------

          pusher.bind('client-guest-new-message', function(data){

              helpers.displayChatMessage(data)

          })

        // ----------------------------------------------------

        // ثبت رویدادهای صفحه

        // ----------------------------------------------------

        chatReplyMessage.on('submit', helpers.replyMessage)

        chatRoomsList.on('click', 'li', helpers.loadChatRoom)

    }())

درست به مانند app.js، ما آبجکت helpers را داریم که گوشته اسکریپت را نگه می‌دارد، و در پایین آن، listenerها و رویدادها فراخوانی شده و ثبت شده‌اند.

کلیدهای PUSHER_APP_* را با کلیدهای داشبورد Pusher خود جایگزین کنید.

اجرای برنامه

می‌توانیم با استفاده از این دستور، برنامه را آزمایش کنیم:

$ flask run

حال اگر از آدرس‌های 127.0.0.1:5000 و 127.0.0.1:5000/admin بازدید کنیم، می‌توانیم برنامه را آزمایش کنیم:

نتیجه گیری

در این مقاله، استفاده از قدرت Pusher در ساخت یک ویجت چت به کمک backend پایتون را یاد گرفتیم. تمام کد مربوط به این آموزش، در این صفحه گیت‌هاب موجود است.

منبع

مقالات پیشنهادی

ساخت یک برنامه چت Realtime با استفاده از اندروید، Node.js و Socket.io

WebSocketها ابزار زیبایی هستند که ما را قادر می‌سازند تا یک ارتباط Realtime در وب‌اپلیکیشن‌های مدرن برقرار کنیم. در واقع،‌ این مکانیزم بسیار قدرتمند ب...

آیکون های زیبا و کاربردی طراحی وب | سری سوم

آیکون ها امروزه نقش کلیدی در طراحی وب دارند بطوری که حتی برای نشون دادن اطلاعات ازشون استفاده میشه . این آیکون ها چیزی از بوجود اومدنشون نمیگذره و بوس...

با استفاده از Billboardjs.js، نمودارهای داده‌ای بر پایه JavaScript بسازید

گرافیک و ویژگی‌های بصری، نقش حیاتی‌ای در پیشرفت محتویات وب بازی می‌کنند. با فناوری وب مدرن، اضافه کردن ویژگی‌های بصری سفارشی مانند آیکون‌های SVG در صف...

آیکون های فروشگاهی و بازاریابی

در این پست لذت بخش من میخوام به شما یک مجموعه از آیکون های زیبا و ضررویه بازاریابی و فروشگاهی رو معرفی کنم که شامل +100 آیکون Swificons با 3 نوع مختلف...