Building a chat widget with Go and JavaScript

Building a chat widget with Go and JavaScript

In this tutorial, we will see how to build a realtime chat widget with Go, Pusher, and JavaScript.

Introduction

The process of building products for an online demographic should be thorough and follow modern-day trends. One of such trend is making it possible for the customers and support agents to have realtime discussions over some form of two-way message channel. This would ensure that customers do not click away in confusion and switch to competitors in times of frustration.

In this tutorial, we will see how to build a realtime chat widget with Go, Pusher, and JavaScript. Here’s a demo of the application:

In the above image, we built a website that sells motorcycles and integrates a chat widget. A customer is able to sign up to speak with a support agent and the agent on the other end can manage communication among a number of connected customers.

Prerequisites

To follow along with this article, you will need the following:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

Skip the next section if you have already signed up with Pusher and created an application.## Setting up Pusher

The realtime feature of the chat widget is dependent on Pusher so you need to create an account here if you don’t already have one, after the signup process, you will be asked to create a new application. Let’s keep the app credentials nearby because we will need it to integrate Pusher within the cat widget.

Enabling client events

The final thing we will do is enable the Pusher application to trigger events from the client (browser) over a private channel. We need this feature because it is what will make it possible for a support agent to securely chat with a customer without having to send the message through the backend server first. Follow the steps below to activate client events from the dashboard:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

That’s all we need to do here.

Setting up the codebase

Let’s begin by navigating into the src directory that is located in the $GOPATH and creating a new directory for our app. This will be the root directory for this project:

    $ cd $GOPATH/src
    $ mkdir go-pusher-chat-widget
    $ cd go-pusher-chat-widget


Let’s create the main Go file (this is the entry point of the application) here and call it chat.go. Next, we will install the Go Pusher library that we will reference within the code for the backend server. Run the following code in the terminal to pull in the package:

    $ go get github.com/pusher/pusher-http-go


Skip the next section if you have already signed up with Pusher and created an application.
Open the chat.go file in your favorite IDE and update it with the following code:

    // File: ./chat.go
    package main

    // Here, we import the required packages (including Pusher)
    import (
        "encoding/json"
        "fmt"
        "io/ioutil"
        "log"
        "net/http"
        pusher "github.com/pusher/pusher-http-go"
    )

    // Here, we register the Pusher client
    var client = pusher.Client{
        AppId:   "PUSHER_APP_ID",
        Key:     "PUSHER_APP_KEY",
        Secret:  "PUSHER_APP_SECRET",
        Cluster: "PUSHER_APP_CLUSTER",
        Secure:  true,
    }

    // Here, we define a customer as a struct
    type customer struct {
        Name  string `json:"name" xml:"name" form:"name" query:"name"`
        Email string `json:"email" xml:"email" form:"email" query:"email"`
    }

    func main() {

        // Serve the static files and templates from the public directory
        http.Handle("/", http.FileServer(http.Dir("./public")))

        // -------------------------------------------------------
        // Listen on these routes for new customer registration and User authorization,
        // thereafter, handle each request using the matching handler function.
        // -------------------------------------------------------
        http.HandleFunc("/new/customer", broadcastCustomerDetails)
        http.HandleFunc("/pusher/auth", pusherAuth)

        // Start executing the application on port 8070
        log.Fatal(http.ListenAndServe(":8070", nil))
    }

In the code above, we registered a new Pusher client with the credentials from the app we created earlier on the dashboard.

Skip the next section if you have already signed up with Pusher and created an application.
In the main function, we defined two endpoints, /new/customer and /pusher/auth. The first will be hit when a new customer signs up and the last will authorize the users so they can subscribe to private channels.

We will be serving all static files from a public directory that we will create shortly.

Skip the next section if you have already signed up with Pusher and created an application.
We also defined customer as a struct and attached extra definitions to its properties so that Go knows how to handle incoming payloads and bind their various structures with a new instance of the customer struct.

Let’s create the handler functions for the endpoints, add this code to the chat.go file just before the main function:

    // File: ./chat.go

    // [...]

    func broadcastCustomerDetails(rw http.ResponseWriter, req *http.Request) {
        body, err := ioutil.ReadAll(req.Body)
        if err != nil {
            panic(err)
        }
        var newCustomer customer
        err = json.Unmarshal(body, &newCustomer)
        if err != nil {
            panic(err)
        }
        client.Trigger("one-to-many", "new-customer", newCustomer)
        json.NewEncoder(rw).Encode(newCustomer)
    }

    func pusherAuth(res http.ResponseWriter, req *http.Request) {
        params, _ := ioutil.ReadAll(req.Body)
        response, err := client.AuthenticatePrivateChannel(params)
        if err != nil {
            panic(err)
        }
        fmt.Fprintf(res, string(response))
    }

    // [...]

Above we have two functions. broadcastCustomerDetails receives a new customer’s details and binds it to an instance of the customer struct. We then trigger the received details over to the admin dashboard in an event over the public channel. The pusherAuth authorizes users so they can subscribe to private channels.

This is all the code required for the backend server to work, let’s move on to the frontend.

Building the frontend

In this section, we will start building the frontend of the web application. We will create all the static files that are rendered when a browser is pointed to the address of our application.

Create a new folder in the project directory and call it public, this folder is the root directory for all of our frontend files. In this folder, create three folders css, js and img.

Next, create two files in the root of the public directory named index.html and support.html.

Creating the homepage

Open the index.html file and update it with the following code:

    <!-- File: ./public/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>X-Cycles</title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
        <link rel="stylesheet" href="./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">X-Cycles</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">X-cycles</h1>
                <p class="lead">We sell the best motorcycles around.</p>
                <p class="lead">
                  <a href="#" class="btn btn-lg btn-secondary">GALLERY</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="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
        <script type="text/javascript" src="./js/app.js"></script>
      </body>
    </html>

In the css directory, create an app.css file and update it with the following code:

    /* File: ./public/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
     */

    .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;
      }

      /* Handle the widths */
      .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;
      }

    }

Skip the next section if you have already signed up with Pusher and created an application.
Now let’s include some JavaScript. In the js directory, create an app.js file and paste the following code:

    // File: ./public/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
                })

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

            LogIntoChatSession: function (evt) {
                const name  = $('#fullname').val().trim()
                const email = $('#email').val().trim().toLowerCase()

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

                if ((name !== '' && name.length >= 3) && (email !== '' && email.length >= 5)) {
                    axios.post('/new/customer', {"name":name, "email":email}).then(response => {
                        chat.name = name
                        chat.email = email
                        console.log(response.data.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)
    }());

Above, we instantiated a Pusher object instance and then we created a helpers object. In this object lies the meat of the script. In the helpers object we have a few methods that do specific tasks:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

Skip the next section if you have already signed up with Pusher and created an application.### Creating the support dashboard

Open the support.html file and update it with the following code:

    <!-- File: ./public/support.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>X-Cycles | Support </title>
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
        <link rel="stylesheet" href="./css/support.css" >
      </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="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
        <script type="text/javascript" src="./js/support.js"></script>
      </body>
    </html>

Let’s write the style for the support page. In the css directory, create a support.css file and paste the following code:

    /* File: ./public/css/support.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;
    }

Now let’s add the JavaScript for the page. In the js directory, create a support.js file and update it with the following code:

    // File: ./public/js/support.js
    (function () {
        var pusher = new Pusher('PUSHER_APP_KEY', {
            authEndpoint: '/pusher/auth',
            cluster: 'PUSHER_APP_CLUSTER',
            encrypted: true
        });

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

        var generalChannel = pusher.subscribe('one-to-many');

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

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

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

Above, the script looks almost similar to the app.js script. The helpers object contains the following functions:

  • An IDE of your choice like Visual Studio Code.
  • Go (version >= 0.10.x) installed on your computer. Here’s how you can install Go.
  • Basic knowledge of the Go programming language.
  • Basic knowledge of JavaScript (ES6) and jQuery.
  • Once you have all the above requirements, we can proceed.

After declaring the helpers, we bind to the Pusher channel and register our listeners.

Skip the next section if you have already signed up with Pusher and created an application.## Running the application

To test the application, we will run the application by typing this command in the terminal:

    $ go run chat.go


We can visit these addresses, http://127.0.0.1:8070 and http://127.0.0.1:8070/support.html, on a web browser using different windows to test that the application works correctly. Here’s what we should see:

Conclusion

In this tutorial, we learned how to create a basic realtime web chat widget using Go and JavaScript. The source code for this project is available here on GitHub.

Learn More

Introducing TensorFlow.js: Machine Learning in Javascript

Express.js & Node.js Course for Beginners - Full Tutorial

Full Stack Developers: Everything You Need to Know

ES5 to ESNext — here’s every feature added to JavaScript since 2015

5 Javascript (ES6+) features that you should be using in 2019

12 Concepts That Will Level Up Your JavaScript Skills

Moving from NodeJS to Go

The Complete JavaScript Course 2019: Build Real Projects!

JavaScript Bootcamp - Build Real World Applications

JavaScript: Understanding the Weird Parts

Vue JS 2 - The Complete Guide (incl. Vue Router & Vuex)

The Full JavaScript & ES6 Tutorial - (including ES7 & React)

Google’s Go Essentials For Node.js / JavaScript Developers

Google’s Go Essentials For Node.js / JavaScript Developers

Keep checking this article as I will make it more comparative between Go and Node constructs. This article will be a brain dump of all I’ve learned so far about Google’s Go language.

Keep checking this article as I will make it more comparative between Go and Node constructs. This article will be a brain dump of all I’ve learned so far about Google’s Go language.

As an advanced JavaScript developer, The more I work with JavaScript the more I understand the advantages of a statically typed language.

Paradoxically, it is easier to make TypeErrors in JavaScript than in other languages I have used. When you do work on the back-end, it is unacceptable. On the front end, when you are a little bit serious about your work, you notice how so many JS developer do NOT understand the typing system in JavaScript because it is not forced on them. That’s why there is a lot of garbage JS code in the world.

JavaScript is HARD when you get serious about understanding it under the hood. So many (kind of hidden) complex concepts you have to understand in order to make sense of the errors in your code (or why it’s working but that’s not the correct way to do it…).

JavaScript is fascinating but sometimes you want to try something else and broaden your horizon.

This article will be a brain dump of all I’ve learned so far about Google’s Go language.

I assume you have installed the Go binaries on your computer.

Variables

package main

import "fmt"

func main() {
    // you MUST use a declared variable otherwise compilation error
    
    var toto int8 = 123
    fmt.Println(toto)

    var tito = 123
    fmt.Println(tito)

    // variable declaration with type inference
    toti := 123
    fmt.Println(toti)

    // variable declaration (implicitly initialized to zero-value,// for numeric types = 0)
    var tata int
    fmt.Println(tata)

    // variable assignment
    tata = 951
    fmt.Println(tata)
}

Number Types

package main

import "fmt"

func main() {
    // Go does not convert types automatically
    // need to explicitly convert them
    var (
        i int8    = 20
        f float32 = 5.6
    )
    fmt.Println(i + int8(f+1.9))

    var (
        j int32 = 456
        k int64 = 987654
    )
    fmt.Println(int64(j) + k)

    // byte is an alias for uint8
    // no need to convert uint8 to byte because same
    var (
        l byte  = 123
        m uint8 = 45
    )
    fmt.Println(l + m)

    // int is an alias for int32 or int64, // dedpending on your mqchine's integer value
    var (
        n int32 = 324
        o int   = 84529899
    )
    fmt.Println(int(n) + o)

    // uint is an alias for uint32 or int64
    var (
        p uint   = 999
        q uint64 = 9999
    )
    fmt.Println(p + uint(q))

    // float operations do not produce an exact result after n decimals// like in most languages
    myFloat := 1.000
    myFloat2 := .999
    fmt.Println(myFloat - myFloat2)

    // arithmetic operations
    fmt.Printf("%d + %d = %d \n", 25, 39, 25+39)
    fmt.Printf("%d - %d = %d \n", 25, 39, 25-39)
    fmt.Printf("%d * %d = %d \n", 25, 39, 25*39)
    fmt.Printf("%d / %d = %v \n", 25, 39, 25/39)
    fmt.Printf("%d %% %d = %v \n", 25, 39, 25%39)

    // constants
    const goldenRatio float64 = 1.6180327868852
    fmt.Printf("The golden ration approximately %f \n", goldenRatio)
    fmt.Printf("The ration truncated to the 3rd decimal is %.3f \n", goldenRatio)

    // formatted printing for numeric types
    fmt.Printf("decimal is %d \n", 99)
    fmt.Printf("binary is %b \n", 99)
    fmt.Printf("unicode reference is %c \n", 99)
    fmt.Printf("hexadecimal is %x \n", 99)
    fmt.Printf("scientific notation of goldenRatio is %e \n", goldenRatio)
}

String Types

package main

import "fmt"

func main() {
    // zero-value for strings is an empty string ""
    var str string
    fmt.Println("\"" + str + "\"")

    str = "This is a string."
    fmt.Println(str)

    str2 := "Another string."
    fmt.Println(str2)

    // raw string literalss with back ticks
    // can be written on multiple lines and no escapes
    str3 := `
        Raw string in the building.
        And another line.
    `
    fmt.Println(str3)

    str4 := "Dunya nzuri = "
    str5 := "美麗的世界"
    str6 := str4 + str5
    fmt.Println(str6)

    // In Go, strings are immutable sequences of bytes
    // you can access each byte
    str7 := "mazoezi"
    b1 := str7[0]
    b2 := str7[1]
    fmt.Println(str7, "\n\t", b1, "=", string(b1), b2, "=", string(b2))

    // substrings
    s1 := str7[0:2]
    s2 := str7[2:4]
    s3 := str7[:3]
    s4 := str7[3:]
    fmt.Printf("%s \t %s \t %s \t %s \n", s1, s2, s3, s4)

    // length of string
    fmt.Println(str7, " = ", len(str7), " characters")

    // single character = rune -> numeric type, sane as int32
    // can be converted to a string
    var r rune 
    // single qutoes
    r = '✖'         // same as r = 10006
    fmt.Println("This is a rune : ", r, " which in string = ", string(r))
}

If and For Statements

package main

import "fmt"

func main() {
    a := 27

    // no parentheses surrounding the condition
    // the body of the if statement MUST be surrounded with {} // no matter what
    // no truthy values
    if a > 25 {
        fmt.Println("a is greater than 25")
    } else {
        fmt.Println("a is less than 25")
    }

    b := 546

    // if statement have block scope
    if b == 546 {
        // c does not exist outside of the if
        c := 54
        fmt.Println(b + c)
    }

    // can declare a variable available ONLY in if and else block
    if d := 44; b < 25 {
        fmt.Println("a is greater than 25", d)
    } else {
        fmt.Println("a is less than 25", d)
    }

    // for loop, no parentheses around signature
    e := 2
    for index := 0; index < 10; index++ {
        if index+e == 2 {
            continue
        }
        if index > 8 {
            break
        }
        fmt.Println("index =", index)

    }

    // equivalent of while statement
    f := 0
    for f < 6 {
        fmt.Println("f =", f)
        // don't forget to have smth allowing to get out of the loop
        f++
    }

    // infinite loop
    g := 0
    for {
        fmt.Println("g =", g)
        g++
        // to stop it at some point
        if g > 30 {
            break
        }
    }

    // for range loop
    
    h := "this is great!"
    for k, v := range h {
        fmt.Println("offset (position) =",k,", value as rune =", v, ",value as string = ", string(v))
    }

    // logical operators
    fmt.Printf("%t && %t is %t \n", true, false, true && false)
    fmt.Printf("%t || %t is %t \n", true, false, true || false)
    fmt.Printf("!%t is %t \n", true, !true) 
}

Functions

package main

import "fmt"

func main() {
    addNumbers(353454, 99999)
    addNumbers(353, 9999)
    addNumbers(3554, 99)

    a := addInts(99, 1)
    fmt.Println("a = ", a)

    a = addInts(9, 675)
    fmt.Println("a = ", a)

    div, remainder := divAndRemainder(57, 7)
    fmt.Println(div, remainder)

    // use underscore to ignore a returned value
    div, _ = divAndRemainder(57, 7)
    fmt.Println(div)

    _, remainder = divAndRemainder(57, 7)
    fmt.Println(remainder)

    divAndRemainder(57, 7)

    // in Go, all functions calls are done by value// (exceptions, see later)
    // a copy of input argument variables is passed o the function
    y := 5
    arr := [2]int{45, 99}
    s := "olo"
    doubleFail(y, arr, s)
    fmt.Println("outside doublefail", y, arr, s)
}

// where function is placed does not matter
// no overloading of function w/ different input parameters
func addNumbers(a int, b int) {
    fmt.Println(a + b)
}

func addInts(c int, d int) int {
    return c + d
}

// multiple returns
func divAndRemainder(e int, f int) (int, int) {
    return e / f, e%f
}

func doubleFail(a int, arr [2]int, s string)  {
    a = a * 2
    for index := 0; index < len(arr); index++ {
        arr[index] *= 2
    }
    s = s + s
    fmt.Println("in doublefail", a, arr, s)
}

Pointers

// pointers are used in C to simulate arrays and strings
package main

import "fmt"

// pointer as input parameter
func setTo10(pointerToInt *int) {
    *pointerToInt = 10
}

func setTo10Fail(pointerToInt *int) {
    fmt.Println("#setTo!(Fail pointer passed as argument =", pointerToInt)
    // will not affect the original pointer because passed by value
    pointerToInt = new(int)
    fmt.Println("#setTo!(Fail reassignment =", pointerToInt)

    // set the value in memory to 10
    *pointerToInt = 10
}

func main() {
    a := 10
    // & = reference / pointer to variable "a"
    // the value of b is the location where a is stored
    b := &a
    // c is a copy of "a" at a given time, // they are independent of each other after the first assignment
    c := a
    fmt.Println(a, b, *b, c)

    a = 20
    // to see the value inside the memory location use * // (de-reference the pointer and get to the value)
    fmt.Println(a, b, *b, c)

    // dereference the pointer and assign a value in memory
    // therefore the value of "a" also changes
    *b = 30
    fmt.Println(a, b, *b, c)

    c = 40
    fmt.Println(a, b, *b, c)

    // zero-value for a pointer is nil (absence of value)
    var d *int
    fmt.Println("value of d =", d)
    // cannot read or write value of a nil pointer
    // fmt.Println(*d)   // throws a panic

    e := new(int)
    // new keyword makes a pointer for the type
    fmt.Println("pointer to e =", e)
    // new also allocates memory, here to the zero-value forint type// therefore no panic
    fmt.Println("value of e =", *e)

    f := 20
    fmt.Println("value of f =", f)
    // we pass a pointer to f into that function
    setTo10(&f)
    fmt.Println("value of f after setTo10 =", f)

    g := 30
    fmt.Println("pointer to g =", &g)
    fmt.Println("value of g =", g)
    // on Go, variable in function calls are passed by VALUE
    setTo10Fail(&g)
    fmt.Println("pointer to g after #setTo10Fail =", &g)
    // you CANNOT change the pointer of a variable passed
    fmt.Println("value of g after #setTo10Fail =", g)
}

Arrays

package main

import "fmt"

func main() {
    // zero-value of arrays is an array// of specified length of zero-values of the type inside the array
    var myArrInt [4]int
    fmt.Println(myArrInt)

    myArrInt[0] = 12
    myArrInt[1] = 23
    myArrInt[2] = 34
    myArrInt[3] = 45
    fmt.Println(myArrInt)

    // one-line array assihnement in a composite literal expression
    // the length of the array is part of its type definition
    myArrInt = [4]int{111, 222, 333, 444}
    fmt.Println(myArrInt)

    myArrStr := [4]string{"titi", "tooi", "tatu", "teti"}
    fmt.Println(myArrStr)

    // iterate over an array
    for i, val := range myArrInt {
        fmt.Printf("At index %d = %d \n", i, val)
    }

    // slice of an array
    myArrStr2 := myArrStr[:2]

    // use _ to ignore the index variable
    for _, val := range myArrStr2 {
        fmt.Println(val)
    }
    // use of arrays is limited, slices are more flexible

}

Slices and Maps

package main

import "fmt"

func main() {
    /*
      SLICE = growable sequence of values of a single specified type
      the size is not part of the type definition
    */
    // define a slice in a composite literal expression
    myFiboSlice := []int{0, 1, 2, 3, 5, 8, 13}
    fmt.Println("myFiboSlice is", myFiboSlice)

    // create a slice from another slice = slice expression
    myFiboSlice2 := myFiboSlice[1:4]
    fmt.Println("myFiboSlice2 is", myFiboSlice2)

    // carefully with subslices because// they point to the same location in memory as the original slice
    myFiboSlice[2] = 00
    fmt.Println("\n myFiboSlice is", myFiboSlice)
    // slices are reference types, behave like pointers
    fmt.Println("myFiboSlice2 after modifying myFiboSlice is", myFiboSlice2)

    // zero-value for slice is nil slice (no value in slice)
    titiSlice := []string{"titi_one", "titi_two", "titi_three"}
    var totoSlice []string
    fmt.Println("totoSlice is", totoSlice)
    fmt.Println("length of totoSlice is", len(totoSlice))

    // assigning slice to another slice makes them share same location// in memrory
    totoSlice = titiSlice
    fmt.Println("totoSlice is now", totoSlice)
    titiSlice[0] = "titi_zero"
    fmt.Println("after modifying titiSlice, totoSlice is now", totoSlice)

    // this behavior also happens in functions
    modifySlice(titiSlice)
    fmt.Println("after modifying titiSlice in a function, totoSlice is now", totoSlice)

    // define a slice filled with wero-values of the type specified
    // last argument is the capacity of the underlying array
    s1 := make([]int, 5, 20)

    // copy a slice from another slice with the copy built-in function// "copy in slice s1 the elements of slice myFiboSlice
    copy(s1, myFiboSlice) 
    fmt.Println("\ns1 is", s1)

    // append built-in function -> returns a new slice
    // increases the length of the slice
    s2 := append(s1, 21, 34, 55)
    fmt.Println("s2 is", s2)

    // append a slice to another slice
    s3 := []int{111, 222, 333}
    // notice the ... after the s3 identier to spread the elements in it
    s3 = append(s2, s3...)
    fmt.Println("s3 is", s3)

    // deleting element at offset 6 (7th) from slice (the easy way)
    s3 = append(s3[:6], s3[7:]...)
    fmt.Println("s3 is", s3)

    // deleting from slice, the more involved way (see the function below)// it was tough to find a working algorithm !
    s3 = deleteItemFromSliceAtIndex(s3, 3)
    fmt.Println("s3 is", s3)

    // slices are based on an underlying array // for which you can specify the capacity // (number of spots to allocate in memory)
    // here the slice is initialized with 5 zero-value spots // but the underlying array has 100 spots in case we append.
    // This allows to avoid to copy and create a new underlying array // every time we append more than the length of the slice
    s4 := make([]int, 5, 100)
    fmt.Println("s4 is", s4)
    fmt.Println("length of s4 is", len(s4))
    fmt.Println("capacity of s4 is", cap(s4))

    // make a slice of bytes out of a string
    hello := "李先生你好"
    myByteSlice := []byte(hello)
    fmt.Println("\nmyByteSlice is", myByteSlice)

    myRuneSlice := []rune(hello)
    fmt.Println("\nmyRuneSlice is", myRuneSlice)

    // multidimensional slice
    s5 := []int{84, 64, 44}
    s6 := []int{42, 32, 22}
    s7 := [][]int{s5, s6}
    fmt.Println("\ns7 is", s7)

    /********** MAPS ****************/

    // associate value of single data type to value of another data type// collection of key / value pairs (key not restricted to a string)
    // maps are unordered

    myMap := make(map[string]string)
    myMap["name"] = "GOTO"
    myMap["firstname"] = "Florian"
    myMap["occupation"] = "Software Engineer"
    myMap["native_language"] = "French"
    fmt.Printf("\n%v\n", myMap)

    // access value in map
    fmt.Printf("The name is %v\n", myMap["name"])

    // if no value on a key return zero-value for the type of value
    fmt.Printf("The age is %v\n", myMap["age"])

    // make sure a key is in the map = comma ok idiom
    // v = value associated w/ existing key
    // ok = boolean is key in map
    if v, ok := myMap["isBillionaire"]; ok {
        fmt.Println("isBillionaire in map =", v)
    } else {
        // ok == false
        fmt.Println("isBillionaire in map =", ok)
    }

    // map literal declaration (composite literal expression)
    worldMap := map[int]string{
        // every line must end with a comma
        1: "nimefurahi kukuona",
        2: "приємно бачити вас",
        3: "तुम्हें देखकर अच्छा लगा",
        4: "ስለተያየን ደስ ብሎኛል",
        5: "ดีใจที่ได้พบคุณ",
    }

    // iterate over a map - order of iteration is random
    for keyInMap, valueInMap := range worldMap {
        fmt.Println(keyInMap, "=", valueInMap)
    }

    // delete value from map (built-in function)
    delete(worldMap, 2)
    fmt.Println("worldMap :", worldMap)

    // same as slices, maps are passed by reference

    // nil map
    var tMap map[string]int
    // writing to zero-valued map will make the program panic
    // tMap["toto"] = 12345678
    fmt.Println("tMap :", tMap)
    fmt.Println("length of tMap :", len(tMap))

    // delete an element in map
    sport := map[string]string{"yoyo": "ok", "ping pong": "great"}
    fmt.Println("sport :", sport)
    delete(sport, "yoyo")
    fmt.Println("sport :", sport)

    // to make sure that you delete an existing pair in the map
    langs := map[string]string{
        "ES6+":         "great",
        "Go":           "cool",
        "TypeScript":   "ok",
        "Python":       "over hyped but nice",
        "Bash":         "necessary",
        "HTML5":        "necessary",
        "CSS":          "necessary",
        "Elm":          "niche",
        "Java":         "no comments...",
        "Rust":         "to assess",
        "Web Assembly": "who knows",
    }
    fmt.Println("lang :", langs)
    if _, ok := langs["assembly"]; ok {
        delete(langs, "assembly")
    }
    fmt.Println("lang :", langs)

}

func modifySlice(s []string) {
    s[1] = "one"
    fmt.Println("\n", s)
}

func deleteItemFromSliceAtIndex(s []int, index int) []int {
    temp := make(map[int]int, len(s))
    // convert slice to map
    for i, v := range s {
        if i == index {
            continue
        }
        temp[i] = v
    }

    newSlice := make([]int, len(temp))

    for i := 0; i < len(temp); i++ {
        if i == index {
            // comma dot idiom = check if key in map
            value, ok := temp[i+1]

            // check that next index exists
            if ok && i <= len(temp) {
                newSlice[i] = value
                continue
            }
        }

        if i > index {
            newSlice[i] = temp[i+1]
            continue
        }

        newSlice[i] = temp[i]
    }

    return newSlice
}

I won’t go into concurrency and the like (Goroutines, channels…) here which are more advanced topics that make Go apart from other languages.

Keep learning new things, never stops !

JavaScript Tutorial: if-else Statement in JavaScript

JavaScript Tutorial: if-else Statement in JavaScript

This JavaScript tutorial is a step by step guide on JavaScript If Else Statements. Learn how to use If Else in javascript and also JavaScript If Else Statements. if-else Statement in JavaScript. JavaScript's conditional statements: if; if-else; nested-if; if-else-if. These statements allow you to control the flow of your program's execution based upon conditions known only during run time.

Decision Making in programming is similar to decision making in real life. In programming also we face some situations where we want a certain block of code to be executed when some condition is fulfilled.
A programming language uses control statements to control the flow of execution of the program based on certain conditions. These are used to cause the flow of execution to advance and branch based on changes to the state of a program.

JavaScript’s conditional statements:

  • if
  • if-else
  • nested-if
  • if-else-if

These statements allow you to control the flow of your program’s execution based upon conditions known only during run time.

  • if: if statement is the most simple decision making statement. It is used to decide whether a certain statement or block of statements will be executed or not i.e if a certain condition is true then a block of statement is executed otherwise not.
    Syntax:
if(condition) 
{
   // Statements to execute if
   // condition is true
}

Here, condition after evaluation will be either true or false. if statement accepts boolean values – if the value is true then it will execute the block of statements under it.
If we do not provide the curly braces ‘{‘ and ‘}’ after if( condition ) then by default if statement will consider the immediate one statement to be inside its block. For example,

if(condition)
   statement1;
   statement2;

// Here if the condition is true, if block 
// will consider only statement1 to be inside 
// its block.

Flow chart:

Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate If statement 

var i = 10; 

if (i > 15) 
document.write("10 is less than 15"); 

// This statement will be executed 
// as if considers one statement by default 
document.write("I am Not in if"); 

< /script> 

Output:

I am Not in if
  • if-else: The if statement alone tells us that if a condition is true it will execute a block of statements and if the condition is false it won’t. But what if we want to do something else if the condition is false. Here comes the else statement. We can use the else statement with if statement to execute a block of code when the condition is false.
    Syntax:
if (condition)
{
    // Executes this block if
    // condition is true
}
else
{
    // Executes this block if
    // condition is false
}


Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate If-else statement 

var i = 10; 

if (i < 15) 
document.write("10 is less than 15"); 
else
document.write("I am Not in if"); 

< /script> 

Output:

i is smaller than 15
  • nested-if A nested if is an if statement that is the target of another if or else. Nested if statements means an if statement inside an if statement. Yes, JavaScript allows us to nest if statements within if statements. i.e, we can place an if statement inside another if statement.
    Syntax:
if (condition1) 
{
   // Executes when condition1 is true
   if (condition2) 
   {
      // Executes when condition2 is true
   }
}

Example:

<script type = "text/javaScript"> 

// JavaScript program to illustrate nested-if statement 

var i = 10; 

if (i == 10) { 

// First if statement 
if (i < 15) 
	document.write("i is smaller than 15"); 

// Nested - if statement 
// Will only be executed if statement above 
// it is true 
if (i < 12) 
	document.write("i is smaller than 12 too"); 
else
	document.write("i is greater than 15"); 
} 
< /script> 

Output:

i is smaller than 15
i is smaller than 12 too
  • if-else-if ladder Here, a user can decide among multiple options.The if statements are executed from the top down. As soon as one of the conditions controlling the if is true, the statement associated with that if is executed, and the rest of the ladder is bypassed. If none of the conditions is true, then the final else statement will be executed.
if (condition)
    statement;
else if (condition)
    statement;
.
.
else
    statement;


Example:

<script type = "text/javaScript"> 
// JavaScript program to illustrate nested-if statement 

var i = 20; 

if (i == 10) 
document.wrte("i is 10"); 
else if (i == 15) 
document.wrte("i is 15"); 
else if (i == 20) 
document.wrte("i is 20"); 
else
document.wrte("i is not present"); 
< /script> 

Output:

i is 20