Talking to Python from JavaScript (and Back Again!)

Talking to Python from JavaScript (and Back Again!)

Something a lot of beginners struggle with is the concept of passing data between different programming languages.

It's far more simple to understand that a value exists in a variable which can be passed around from function to function. However, to go beyond the program's edges we must serialized our data in some way. We'll look at two ways that these two languages can communicate. AJAX requests via the new Fetch API, and piping between local processes.

Serializing data means taking a value, object, or data structure, and translating it into a format that can be stored or transmitted. Most importantly, it needs to be put back together at the other end. Let’s take look at JavaScript Object Notation (JSON). JSON is a human-readable format and is straightforward for machines to read and write. The specification is small enough to read during one cup of coffee. Both JavaScript and Python have standard library methods to parse and write JSON.

JSON Crash Course

JSON is built on two data structures. Objects — key/value pairs like a JavaScript Object, and a Python Object or DictionaryArrays — a series of data like a JavaScript Array, and a Python List.

/* JavaScript
   Try this out in your developer console! */

var person = {"name":"Andrew", "loves":"Open Source"};
var asJSON = JSON.stringify(person);

// person is of type 'object'
console.log(person is of type ${typeof person});

// asJSON is of type 'string'
console.log(asJSON is of type ${typeof asJSON});

// We can convert it back to an object by parsing it
// asObject is of type 'object'
var asObject = JSON.parse(asJSON);
console.log(asObject is of type ${typeof asObject});

Let’s do the same in Python by using the standard library module json.

# python

animal = {'type':'cat', 'age':12}
as_json = json.dumps(animal)

print(type(animal)) # prints '<class 'dict'>'
print(type(as_json)) # prints '<class 'str'>'

now back again

as_object = json.loads(as_json)
print(type(as_object)) # prints '<class 'dict'>'

Recap: in JavaScript, you serialize to JSON with JSON.stringify() and parse with JSON.parse(). This works in the browser as well as Node.js. In Python, first import the json module then serialize with json.dumps() and parse with json.loads().

Talking via AJAX

In the past, this would be done with XMLHttpRequest but the relatively new Fetch API is far more pleasant to use. First, we’ll code up a small Python web server, and then we’ll look at passing JSON back and forth with the browser.

Flask is a ‘microframework’. It’s not only extremely fun to use, it’s also great to prototype with. We’ll use its jsonifymodule which writes/parses JSON as well as setting the correct response headers (an application/json mime type). It requires two OS-specific commands to install and run a debug server. For myself on OS X, they were pip install flask and FLASK_APP=hello.py flask run.

Code along or fork this repository to grab the code for all the examples.

# app.py
from flask import Flask, jsonify, request, render_template
app = Flask(name)

@app.route('/hello', methods=['GET', 'POST'])
def hello():

# POST request
if request.method == 'POST':
    print('Incoming..')
    print(request.get_json())  # parse as JSON
    return 'OK', 200

# GET request
else:
    message = {'greeting':'Hello from Flask!'}
    return jsonify(message)  # serialize and use JSON headers

@app.route('/test')
def test_page():
# look inside templates and serve index.html
return render_template('index.html')

With our server running and serving us a page we can run JavaScript on, let’s talk in JSON! We’ll send a GET request with the Fetch API and receive a greeting from Flask. Before writing a single line of client-side code, I always use Postman to test my web servers — it’s free and one of the industry standard tools for API testing and development.

I’m running the following snippets in <script> tags inside templates/index.html. There’s nothing else inside index.html so the rendered page is blank. Everything happens in the console.

// GET is the default method, so we don't need to set it
fetch('/hello')
.then(function (response) {
return response.text();
}).then(function (text) {
console.log('GET response text:');
console.log(text); // Print the greeting as text
});

// Send the same request
fetch('/hello')
.then(function (response) {
return response.json(); // But parse it as JSON this time
})
.then(function (json) {
console.log('GET response as JSON:');
console.log(json); // Here’s our JSON object
})

Awesome! We’ve got Python talking to client-side JavaScript using JSON for data serialization. Let’s flip it and send JSON to Python from the browser. We’ll use the Fetch API again but it will be a POST request instead of GET.

Beginner tip: Remember the difference between POST and GET. When you POST mail, you head to the post office with your letter filled with information. When you GET mail, you head to the post office again but this time you’re picking up something that’s been left for you.

// POST
fetch('/hello', {

// Specify the method
method: 'POST',

// A JSON payload
body: JSON.stringify({
    "greeting": "Hello from the browser!"
})

}).then(function (response) { // At this point, Flask has printed our JSON
return response.text();
}).then(function (text) {

console.log('POST response: ');

// Should be 'OK' if everything was successful
console.log(text);

});

With these two core building blocks, we’ve conquered JSON communication via HTTP. However, do note that you should be adding catch to the end of these Promises. I’ve only trimmed them for clarity. It’s better to handle errors gracefully so we can tell the user that they are disconnected or that there’s an error on our end. Docs for catch here.

If you’re talking to a Node.js web server with Python, you will probably reach for the requests module, which has syntax almost identical to the Fetch API.

Talking via processes

We’re going to spawn processes (both ways) so we can see what communication between Node.js and Python looks like. We’ll listen to the stdout stream of the child process in both instances. Let’s imagine that we have a program that reports data at irregular intervals. A temperature sensor. We want to listen to that program and store the values it reports.

Here’s our fake sensor program in Python. It prints data to stdout. We’ll catch it in Node.js.

# sensor.py

import random, time
while True:
time.sleep(random.random() * 5) # wait 0 to 5 seconds
temperature = (random.random() * 20) - 5 # -5 to 15
print(temperature, flush=True, end='')

When piping information in this way, it’s important to flush the stream so it reaches stdout when you expect it to (why do we fflush() in C?) . More information on Python flushing here. We also make sure that the end of the printed statement contains no extra information (even though parseFloat() would clean it!) by default it would be the newline character \n.

So, we’re Node.js and we want the current temperature as it’s reported. Let’s spawn sensor.py as a process and listen for the stdout event. Piping the data between the two running languages.

// temperature-listener.js

const { spawn } = require('child_process');
const temperatures = []; // Store readings

const sensor = spawn('python', ['sensor.py']);
sensor.stdout.on('data', function(data) {

// Coerce Buffer object to Float
temperatures.push(parseFloat(data));

// Log to debug
console.log(temperatures);

});

Flip it and reverse it

Now, let’s flip those roles. A Node.js sensor and a Python listener! This time we’ll try a different method, using a newline character (\n) to delimit the different readings instead of waiting for an event. We’ll add the data to a buffer until we hit a newline char. Once we do, we’ve collected a full reading and we can store it.

First, the equivalent sensor in Node.js.

// sensor.js

function reportReading() {
const temperature = (Math.random() * 20) - 5; // Range of -5 to 15
process.stdout.write(temperature + '\n'); // Write with newline char
setTimeout(reportReading, Math.random() * 5000); // Wait 0 to 5 seconds
}
reportReading();

Now in Python, a temperature listener program that will spawn the above code as a process.

# temperature-listener.py

import sys
from subprocess import Popen, PIPE

temperatures = [] # store temperatures
sensor = Popen(['node', 'sensor.js'], stdout=PIPE)
buffer = b''
while True:

# read sensor data one char at a time
out = sensor.stdout.read(1)

# after a full reading..
if out == b'\n':
    temperatures.append(float(buffer))
    print(temperatures)
    buffer = b''
else:
    buffer += out  # append to buffer

You can run node temperature-listener.js or python temperature-listener.py and the result will be the same. The array of temperatures will grow as new data arrives, and our debug log line will result in the following output.

[ 3.8075910850643098 ]
[ 3.8075910850643098, -1.5015912681923482 ]
[ 3.8075910850643098, -1.5015912681923482, 11.97817663641078 ]

We’ve seen two different ways of communicating between Python and JavaScript but if either of these aren’t for you — don’t fret! There are many ways to pass data between these two languages. Not limited to: named pipes, TCP sockets, WebSockets, and file polling.


Originally published by Andrew Healey at dev.to

Thanks for reading :heart: If you liked this post, share it with all of your programming buddies! Follow me on Facebook | Twitter

Learn More

☞ Machine Learning with Python, Jupyter, KSQL and TensorFlow

☞ Introduction to Python Microservices with Nameko

☞ Comparing Python and SQL for Building Data Pipelines

☞ Python Tutorial - Complete Programming Tutorial for Beginners (2019)


JavaScript vs Python: Will Python Replace JavaScript popularity by 2020?

JavaScript vs Python: Will Python Replace JavaScript popularity by 2020?

JavaScript is currently the most commonly used programming language but now Python is dishing out some stiff competition. Python has been steadily increasing in popularity so much so that it is now the fastest-growing programming language. So will Python Replace JavaScript popularity by 2020?

This is the Clash of the Titans!!

And no…I am not talking about the Hollywood movie (don’t bother watching it…it’s horrible!). I am talking about JavaScript and Python, two of the most popular programming languages in existence today.

JavaScript is currently the most commonly used programming language (and has been for quite some time!) but now Python is dishing out some stiff competition. Python has been steadily increasing in popularity so much so that it is now the fastest-growing programming language. So now the question is…Will Python Replace JavaScript popularity by 2020?

To understand the above question correctly, it is important to know more about JavaScript and Python as well as the reasons for their popularity. So let’s start with JavaScript first!

Why is JavaScript so popular?

JavaScript is a high-level, interpreted programming language that is most popular as a scripting language for Web pages. This means that if a web page is not just sitting there and displaying static information, then JavaScript is probably behind that. And that’s not all, there are even advanced versions of the language such as Node.js which is used for server-side scripting.

JavaScript is an extremely popular language. And if my word doesn’t convince you, here are the facts!!!

According to StackOverflow Developer Survey Results 2019, JavaScript is the most commonly used programming language, used by 69.7 % of professional developers. And this is a title it has claimed the past seven years in a row.

In addition to that, the most commonly used Web Frameworks are jQuery, Angular.js and React.js (All of which incidentally use JavaScript). Now if that doesn’t demonstrate JavaScript’s popularity, what does?!

Image Source: Stackoverflow

So now the question arises…Why is JavaScript so popular?

Well, some of the reasons for that are:

  • JavaScript is used both on the client-side and the server-side. This means that it runs practically everywhere from browsers to powerful servers. This gives it an edge over other languages that are not so versatile.
  • JavaScript implements multiple paradigms ranging from OOP to procedural. This allows developers the freedom to experiment as they want.
  • JavaScript has a large community of enthusiasts that actively back the language. Without this, it would have been tough for JavaScript to establish the number one position it has.
Can Python Replace JavaScript in Popularity?

Python is an interpreted, general-purpose programming language that has multiple uses ranging from web applications to data analysis. This means that Python can be seen in complex websites such as YouTube or Instagram, in cloud computing projects such as OpenStack, in Machine Learning, etc. (basically everywhere!)

Python has been steadily increasing in popularity so much so that it is the fastest-growing major programming language today according to StackOverflow Developer Survey Results 2019.

This is further demonstrated by this Google Trends chart showing the growth of Python as compared to JavaScript over the last 5 years:

As shown in the above data, Python recorded increased search interest as compared to JavaScript for the first time around November 2017 and it has maintained its lead ever since. This shows remarkable growth in Python as compared to 5 years ago.

In fact, Stack Overflow created a model to forecast its future traffic based on a model called STL and guess what…the prediction is that Python could potentially stay in the lead against JavaScript till 2020 at the least.

Image Source : Stackoverflow

All these trends indicate that Python is extremely popular and getting even more popular with time. Some of the reasons for this incredible performance of Python are given as follows:

  • Python is Easy To Use
    No one likes excessively complicated things and that’s one of the reasons for the growing popularity of Python. It is simple with an easily readable syntax and that makes it well loved by both seasoned developers and experimental students. In addition to this, Python is also supremely efficient. It allows developers to complete more work using fewer lines of code. With all these advantages, what’s not to love?!!
  • Python has a Supportive Community
    Python has been around since 1990 and that is ample time to create a supportive community. Because of this support, Python learners can easily improve their knowledge, which only leads to increasing popularity. And that’s not all! There are many resources available online to promote Python, ranging from official documentation to YouTube tutorials that are a big help for learners.
  • Python has multiple Libraries and Frameworks
    Python is already quite popular and consequently, it has hundreds of different libraries and frameworks that can be used by developers. These libraries and frameworks are really useful in saving time which in turn makes Python even more popular. Some of the popular libraries of Python are NumPy and SciPy for scientific computing, Django for web development, BeautifulSoup for XML and HTML parsing, scikit-learn for machine learning applications, nltk for natural language processing, etc.
So What’s the Conclusion?

While JavaScript is currently the most popular programming language, Python could soon outstrip it of this title based on its incredible growth rate. So it is entirely possible that Python could be the most popular programming language by 2020.

However, this will merely impact the relative popularity of these two languages and not specify which among them is the better language. That choice is entirely subjective and may depend on multiple factors such as project requirements, scalability, ease of learning as well as the future growth prospects.

JavaScript vs Python : Can Python outperform JavaScript in the next five years?

JavaScript vs Python : Can Python outperform JavaScript in the next five years?

JavaScript and Python are two influential programming languages for building a wide range of applications. While JavaScript has been the dominant programming language for many years, Python’s fast-growth threatens to dethrone the widely popular technology.

JavaScript and Python are two influential programming languages for building a wide range of applications. While JavaScript has been the dominant programming language for many years, Python’s fast-growth threatens to dethrone the widely popular technology.

This is the Clash of the Titans!!

And no…I am not talking about the Hollywood movie (don’t bother watching it…it’s horrible!). I am talking about JavaScript** and **Python, two of the most popular programming languages in existence today.

JavaScript is currently the most commonly used programming language (and has been for quite some time!) but now Python is dishing out some stiff competition. Python has been steadily increasing in popularity so much so that it is now the fastest-growing programming language. So now the question is…Will Python Replace JavaScript popularity by 2020?

To understand the above question correctly, it is important to know more about JavaScript and Python as well as the reasons for their popularity. So let’s start with JavaScript first!

Why is JavaScript so popular?

JavaScript is a high-level, interpreted programming language that is most popular as a scripting language for Web pages. This means that if a web page is not just sitting there and displaying static information, then JavaScript is probably behind that. And that’s not all, there are even advanced versions of the language such as Node.js which is used for server-side scripting.

JavaScript is an extremely popular language. And if my word doesn’t convince you, here are the facts!!!

According to StackOverflow Developer Survey Results 2019, JavaScript is the most commonly used programming language, used by 69.7 % of professional developers. And this is a title it has claimed the past seven years in a row.

In addition to that, the most commonly used Web Frameworks are jQuery, Angular.js and React.js (All of which incidentally use JavaScript). Now if that doesn’t demonstrate JavaScript’s popularity, what does?!

Image Source: Stackoverflow

So now the question arises…Why is JavaScript so popular?

Well, some of the reasons for that are:
JavaScript is used both on the client-side and the server-side. This means that it runs practically everywhere from browsers to powerful servers. This gives it an edge over other languages that are not so versatile.JavaScript implements multiple paradigms ranging from OOP to procedural. This allows developers the freedom to experiment as they want.JavaScript has a large community of enthusiasts that actively back the language. Without this, it would have been tough for JavaScript to establish the number one position it has.

Can Python Replace JavaScript in Popularity?

Python is an interpreted, general-purpose programming language that has multiple uses ranging from web applications to data analysis. This means that Python can be seen in complex websites such as YouTube or Instagram, in cloud computing projects such as OpenStack, in Machine Learning, etc. (basically everywhere!)

Python has been steadily increasing in popularity so much so that it is the fastest-growing major programming language today according to StackOverflow Developer Survey Results 2019.

This is further demonstrated by this Google Trends chart showing the growth of Python as compared to JavaScript over the last 5 years:

As shown in the above data, Python recorded increased search interest as compared to JavaScript for the first time around November 2017 and it has maintained its lead ever since. This shows remarkable growth in Python as compared to 5 years ago.

In fact, Stack Overflow created a model to forecast its future traffic based on a model called STL and guess what…the prediction is that Python could potentially stay in the lead against JavaScript till 2020 at the least.

Image Source : Stackoverflow

All these trends indicate that Python is extremely popular and getting even more popular with time. Some of the reasons for this incredible performance of Python are given as follows:
Python is Easy To UseNo one likes excessively complicated things and that’s one of the reasons for the growing popularity of Python. It is simple with an easily readable syntax and that makes it well loved by both seasoned developers and experimental students. In addition to this, Python is also supremely efficient. It allows developers to complete more work using fewer lines of code. With all these advantages, what’s not to love?!!Python has a Supportive CommunityPython has been around since 1990 and that is ample time to create a supportive community. Because of this support, Python learners can easily improve their knowledge, which only leads to increasing popularity. And that’s not all! There are many resources available online to promote Python, ranging from official documentation to YouTube tutorials that are a big help for learners.Python has multiple Libraries and FrameworksPython is already quite popular and consequently, it has hundreds of different libraries and frameworks that can be used by developers. These libraries and frameworks are really useful in saving time which in turn makes Python even more popular. Some of the popular libraries of Python are NumPy and SciPy for scientific computing, Django for web development, BeautifulSoup for XML and HTML parsing, scikit-learn for machine learning applications, nltk for natural language processing, etc.## So What’s the Conclusion?

While JavaScript is currently the most popular programming language, Python could soon outstrip it of this title based on its incredible growth rate. So it is entirely possible that Python could be the most popular programming language by 2020.

However, this will merely impact the relative popularity of these two languages and not specify which among them is the better language. That choice is entirely subjective and may depend on multiple factors such as project requirements, scalability, ease of learning as well as the future growth prospects.

Build a chat widget with Python and JavaScript

Build a chat widget with Python and JavaScript

Build a chat widget with Python and JavaScript - Use Python and JavaScript to create a simple chat widget and admin panel. Users will be able to initiate chat sessions, and admins will be able ...

Build a chat widget with Python and JavaScript - Use Python and JavaScript to create a simple chat widget and admin panel. Users will be able to initiate chat sessions, and admins will be able ...

Building quality digital products is a requirement toward acquiring long-term customers, but inefficient communication is an efficient way to lose them just as quickly as you gain them. The internet is currently the world’s largest marketplace and everyone is building something for an online audience to consume, however, it would be a shame if there isn’t a way to receive feedback or interact with customers in realtime.

In this tutorial, we will look at how we can create a realtime chat widget using Pusher, Python, and JavaScript. When we are done building, the final application will look and work like this:

In the image above, we can see a digital product called “SPIN” and it has a chat widget option for visiting customers to interact with. On the left browser window, a customer visits this website and fills in his/her details before submitting the form.

There is an admin on the right browser window who can see all connected customers and respond to all their messages accordingly, providing effective and realtime support.

Prerequisites

To follow along with this tutorial, a basic knowledge of Python, Flask, JavaScript (ES6 syntax) and jQuery is required. You will also need the following installed:

  1. Python (>= v3.x)
  2. Virtualenv
  3. Flask

Virtualenv is great for creating isolated Python environments, so we can install dependencies in an isolated environment, and not pollute our global packages directory.

Let’s install virtualenv with this command:

    $ pip install virtualenv


⚠️ Virtualenv comes preinstalled with Python 3 so you may not need to install it if you are on this version.## Setting up the app environment

Let’s create our project folder, and activate a virtual environment within it:

    $ 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


Now that we have the virtual environment setup, we can install Flask and the remaining dependencies with this command:

    $ pip install flask flask-cors simplejson


We need to install the Pusher library as we will need that for realtime updates.

Setting up Pusher

The first step here will be to get a Pusher Channels application. We need the application credentials for our realtime messaging to work.

Go to the Pusher website and create an account. After creating an account, you should create a new application. Follow the application creation wizard and then you should be given your application credentials, we will use this later in the article:

There’s one more thing we need to do here on this dashboard; because we will directly be triggering the message events on the client side of the application, we need to turn on a special feature that is turned off by default for security reasons. To learn more about triggering events on the client side, you can read the documentation here.

On the dashboard, click on App settings and scroll to the bottom of the page then select the option that says Enable client events:

Great, now let’s install the Pusher Python library, so that we can use Pusher in the application:

    $ pip install pusher


File and folder structure

Here’s a representation of the file/folder structure for this app:

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


The static folder will contain the static files to be used as is defined by Flask standards. The templates folder will hold the HTML templates. In our application, app.py is the main entry point and will contain our server-side code.

Let’s create the app.py file and then the static and templates folders.

Building the backend

Before we start writing code to determine how the frontend of our application will be rendered, let’s fully develop the backend and all of its endpoints so that the frontend has something to communicate with when we build it.

Let’s open the app.py file and paste the following code:

    // 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'

    # configure pusher object
    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)

⚠️ Virtualenv comes preinstalled with Python 3 so you may not need to install it if you are on this version.
The logic for this application is simple, we will require a Pusher public channel so that whenever a new customer connects with the chat widget, their details are sent over to the admin (using that public channel) and the admin can subscribe to a private channel (the customer will have to subscribe to this private channel too) using the customer’s email as a unique ID. The admin and that customer can further engage in one to one messaging over that private channel.

Let’s go over the code in the app.py file to see how it satisfies the logic we just discussed. We first imported all the required packages, then registered a new Pusher instance. Next, we declared four endpoints:

We used the trigger method on the Pusher instance here, the trigger method has the following syntax: pusher.trigger("a_channel", "an_event", {key: "data"}). You can find the docs for the Pusher Python library here to get more information on configuring and using Pusher in Python.

Building the frontend

In this section, we are going to do the following things:

  • / - This endpoint returns the static HTML template that defines the homepage of this app.
  • /admin - This endpoint returns the static HTML template that defines the admin dashboard.
  • /new/guest/ - This endpoint receives a POST request containing the details of a new customer and pushes it to the public channel — general-channel — in a “new-guest-details” event. The admin on the other side responds to this event by subscribing to a private channel using the user’s email as the unique ID.

We will be using Bootstrap as a base style for the application. We will also be using other third-party libraries so let’s fetch the source and place them in the appropriate directory inside the static directory.

Add these files in the static/js directory:

  1. Python (>= v3.x)
  2. Virtualenv
  3. Flask

Add this file in the static/css directory:

  1. Python (>= v3.x)
  2. Virtualenv
  3. Flask

The new folder structure should be:

    ├── 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


If you currently have this folder structure then you are good to go!

Setting up the homepage view

In the templates/index.html file, paste the following code:

<!-- 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>

In this file, we have the HTML for the homepage. We also used Flask’s url_for function to dynamically link to all the local scripts and styles that we created.

Because we require our application to send and receive messages in realtime, we imported the official Pusher JavaScript library with this line of code:

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

We included some custom classes within the HTML elements, however, these classes will be useless if we do not define them in the matching CSS file, open the static/css/app.css file and paste the following code:

/__ 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;
    }
}

Setting up the admin dashboard view

In the templates/admin.html file, paste the following code:

<!-- 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>&nbsp;</p>
                <div class="chat" style="margin-bottom:150px">
                    <h5 id="room-title"></h5>
                    <p>&nbsp;</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>

Open the static/css/admin.css file and paste the following code:

/** 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;
}

Writing the app.js script

In this section, we will write the script that works with the homepage and supports the customers’ functions. This script will define the logic that will enable a customer to submit the form after filling in his/her details and everything else.

We will define some helper functions within an IIFE and these functions will run on the occurrence of several DOM events and possibly pass on the execution to other helper functions.

Open the app.js file and paste the following:


// File: static/js/app.js
(function() {
    'use strict';

    var pusher = new Pusher('PUSHER_APP_KEY', {
        authEndpoint: '/pusher/auth',
        cluster: 'PUSHER_APP_CLUSTER',
        encrypted: true
    });

    // ----------------------------------------------------
    // Chat Details
    // ----------------------------------------------------

    let chat = {
        name:  undefined,
        email: undefined,
        myChannel: undefined,
    }


    // ----------------------------------------------------
    // Targeted Elements
    // ----------------------------------------------------

    const chatPage   = $(document)
    const chatWindow = $('.chatbubble')
    const chatHeader = chatWindow.find('.unexpanded')
    const chatBody   = chatWindow.find('.chat-window')


    // ----------------------------------------------------
    // Register helpers
    // ----------------------------------------------------

    let helpers = {

        // ----------------------------------------------------
        // Toggles the display of the chat window.
        // ----------------------------------------------------

        ToggleChatWindow: function () {
            chatWindow.toggleClass('opened')
            chatHeader.find('.title').text(
                chatWindow.hasClass('opened') ? 'Minimize Chat Window' : 'Chat with Support'
            )
        },

        // --------------------------------------------------------------------
        // Show the appropriate display screen. Login screen or Chat screen.
        // --------------------------------------------------------------------

        ShowAppropriateChatDisplay: function () {
            (chat.name) ? helpers.ShowChatRoomDisplay() : helpers.ShowChatInitiationDisplay()
        },

        // ----------------------------------------------------
        // Show the enter details form.
        // ----------------------------------------------------

        ShowChatInitiationDisplay: function () {
            chatBody.find('.chats').removeClass('active')
            chatBody.find('.login-screen').addClass('active')
        },

        // ----------------------------------------------------
        // Show the chat room messages display.
        // ----------------------------------------------------

        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)
        },

        // ----------------------------------------------------
        // Append a message to the chat messages UI.
        // ----------------------------------------------------

        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)
            }
        },

        // ----------------------------------------------------
        // Send a message to the chat channel.
        // ----------------------------------------------------

        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('')
        },

        // ----------------------------------------------------
        // Logs user into a chat session.
        // ----------------------------------------------------

        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()
        }
    }

    // ------------------------------------------------------------------
    // Listen for a new message event from the admin
    // ------------------------------------------------------------------

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


    // ----------------------------------------------------
    // Register page event listeners
    // ----------------------------------------------------

    chatPage.ready(helpers.ShowAppropriateChatDisplay)
    chatHeader.on('click', helpers.ToggleChatWindow)
    chatBody.find('#loginScreenForm').on('submit', helpers.LogIntoChatSession)
    chatBody.find('#messageSupport').on('submit', helpers.SendMessageToSupport)
}())

Above we have the JavaScript that powers the clients chat widget. In the code, we start by instantiating Pusher (remember to replace the PUSHER_* keys with the keys in your Pusher dashboard).

We have a helpers property that has a few functions attached to it. Each function has a comment explaining what it does right before it is defined. At the bottom of the script is where we register all the events and listeners that make the widget function as expected.

Writing the admin.js script The code in the admin.js is similar to the app.js and functions in a similat manner. Open the admin.js add paste the following code:


// File: static/js/admin.js
(function () {
    'use strict';

    // ----------------------------------------------------
    // Configure Pusher instance
    // ----------------------------------------------------

    var pusher = new Pusher('PUSHER_APP_KEY', {
        authEndpoint: '/pusher/auth',
        cluster: 'PUSHER_APP_CLUSTER',
        encrypted: true
        });

    // ----------------------------------------------------
    // Chat Details
    // ----------------------------------------------------

    let chat = {
        messages: [],
        currentRoom: '',
        currentChannel: '',
        subscribedChannels: [],
        subscribedUsers: []
    }

    // ----------------------------------------------------
    // Subscribe to the generalChannel
    // ----------------------------------------------------

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

    // ----------------------------------------------------
    // Targeted Elements
    // ----------------------------------------------------

    const chatBody = $(document)
    const chatRoomsList = $('#rooms')
    const chatReplyMessage = $('#replyMessage')

    // ----------------------------------------------------
    // Register helpers
    // ----------------------------------------------------

    const helpers = {

        // ------------------------------------------------------------------
        // Clear the chat messages UI
        // ------------------------------------------------------------------

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

        // ------------------------------------------------------------------
        // Add a new chat message to the chat window.
        // ------------------------------------------------------------------

        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>`
                )
            }
        },

        // ------------------------------------------------------------------
        // Select a new guest chatroom
        // ------------------------------------------------------------------

        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()
        },

        // ------------------------------------------------------------------
        // Reply a message
        // ------------------------------------------------------------------
        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('')
        },
    }


        // ------------------------------------------------------------------
        // Listen to the event that returns the details of a new guest user
        // ------------------------------------------------------------------

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

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

        chat.subscribedUsers.push(data);

        // render the new list of subscribed users and clear the former
        $('#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>`
                )
        })

        })


        // ------------------------------------------------------------------
        // Listen for a new message event from a guest
        // ------------------------------------------------------------------

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


    // ----------------------------------------------------
    // Register page event listeners
    // ----------------------------------------------------

    chatReplyMessage.on('submit', helpers.replyMessage)
    chatRoomsList.on('click', 'li', helpers.loadChatRoom)
}())

Just like in the app.js we have the helpers object that holds the meat of the script and towards the bottom, the listeners and events are called and registered.

⚠️ Virtualenv comes preinstalled with Python 3 so you may not need to install it if you are on this version.

Running the application

We can test the application using this command:

    $ flask run


Now if we visit 127.0.0.1:5000 and 127.0.0.1:5000/admin we should test the application:

Conclusion

In this article, we have learned how we can leverage the power of Pusher in creating a chat widget powered by a Python backend. The entire code for this tutorial is available on GitHub.