Live Auction / Marketplace App Tutorial

This tutorial will guide you through building a live auction / marketplace app in Javascript

Step 01

Real-Time Auction App / Marketplace App Environment Setup

This application is written in JavaScript and requires no special development environment. All you need is a code editor or IDE (e.g. Visual Studio Code) and Git installed.

Setup Git

You need Git to clone the Real-Time Auction App / Marketplace App from the GitHub repository.

Even if you have Git installed, you may want to ensure that it’s the latest version. Follow the Getting Started - Installing Git guide to install Git.

Next, check the Git installation. Open your terminal and run the below command to ensure Git is installed. You should see a version number after you run the command:

git --version

Your development environment is now ready. Next, you need to configure your PubNub API keys for the Real-Time Auction / Marketplace App. 

Step 02

Create keys

Demo Keys

You're ready to go!

We’ve assigned demo keys which are set up to support the features of this tutorial app. These will be active for 72 hours but you can sign up for a free account at the end of the tutorial to create your own permanent keys.

The demo keys have the following functionality activated ready for your application:

  • Channel Presence Be notified when PubNub clients go online or offline.

  • Stream Controller You will be able to receive messages against a group of channels, or post one message to multiple channels.

  • Message Persistence You will be able to see user’s message history and meta data.

My own keys

You'll need to create an account to use your own keys

  1. Sign in to your PubNub Dashboard.

  2. Go to Keysets using the left hand side navigation.

  3. Click the “Create New Keyset” button on the upper right hand-side of the portal.

  4. A pop-up appears. Give the keyset a name and click the “Create” button.

  5. Click on the newly created keyset.

  6. Enable three features for the keyset to be able to connect to the PubNub Network: Presence, Message Persistence and Stream Controller.

  7. Enable Presence by clicking on the slider to turn it on. A pop-up will require that you enter in “ENABLE”. Enter in “ENABLE” in all caps and then press the “Enable” button. You can leave the default settings.

  8. Enable Message Persistence by clicking on the slider to turn it on. You can leave the default settings.

  9. Enable Stream Controller by clicking on the slider to turn it on if it is already not enabled. You can leave the default settings.

  10. Click on the “Save Changes” button in the bottom right corner of the portal. After a few seconds, a confirmation message will appear in the upper-left corner of the portal confirming that these changes have been saved. 

  11. Scroll back to the top of the portal. Copy the Publish key by clicking on the copy icon and paste it below

  12. Copy the Subscribe Key by clicking on the copy icon and paste it below

Step 03

Build and run

Watch the video or follow the steps below to build and run your application.

JavaScript
//This code box will update soon as you
//progress through the video
//Use the copy icon in the top right to quickly copy
//code as you're following along

AUTH0

Please note that although this tutorial will refer to Auth0, this functionality has been removed from the demo app, though can still be found commented out in the code. Logging in will not prompt you for credentials.

Step 1. Download The Real-Time Auction / Marketplace Source Code

Open your terminal and clone the Real-Time Auction / Marketplace repository. This repository contains the open source PubNub powered Real-Time Auction / Marketplace App that we'll explore in this tutorial.

shell
git clone https://github.com/PubNubDevelopers/Auction-Demo

Now you're ready to explore the demo and add your own API keys to get started. Try the complete demo here.

In the next step, we'll review how the demo works from top to bottom and show you how to configure your API keys.

Step 2. Exploring how the app works

This Real-Time Auction App / Marketplace Demo utilizes PubNub for all back-end functionality and has no additional back-end servers or infrastructure. All of the primary features come from the front-end and PubNub's APIs. This demo also uses Auth0 to authenticate and manage users.

Try the finished application here or view the repository here. At the end of this tutorial you'll be able to run this demo locally with your own API keys.

Let's start by reviewing the UI of the app and then we'll cover the JavaScript that makes everything function.

This app uses Bootstrap 5 for managing the layout. Inside the file 'styles.css' there is also some custom styling used on all pages, but none of that affects how this app works so we won't cover that in this tutorial.

The inital landing page, 'index.html' is the default view of the application. This page has placeholder areas for promotional content and a login button that triggers the Auth0 Lock for users to login. The only required item on the page is the login button - everything else can be customized.

login page auction app

At the bottom of this page we include the Auth0 SDK and the file 'login.js' that has the JavaScript code used to start authentication and redirect to 'bidding.html' view.

html
<a class="nav-link text-white" onclick="login()" href="#" >Login Now</a>

 <script src="https://cdn.auth0.com/js/auth0-spa-js/1.19/auth0-spa-js.production.js"></script>
 <script src="https://pubnubdevelopers.github.io/Auction-Demo/login.js"></script> 

The 'bidding.html' view is the primary UI for our auction application.

At the top of the page we have an area for our primary navigation. These links switch the auction room that the user is currently bidding in. Each link represents a category and you can add more categories by simply adding more links here.

There's also a logout button in the navigation area.

html
<nav class="py-3 navbar navbar-expand-lg navbar-dark bg-dark">
    <div class="container d-flex flex-wrap">
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsmenu" aria-controls="navbarsmenu" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
        <div class="collapse navbar-collapse"  id="navbarsmenu">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item"><a href="#" id="your-bids"  onclick="loadBidding('your-bids')" class="nav-link px-2 active" aria-current="page">Your Bids</a></li>
                <li class="nav-item"><a href="#" id="auctions" onclick="loadBidding('auctions')" class="nav-link px-2">Your Auctions</a></li>
                <li class="nav-item"><a href="#" id="Woodwind"onclick="loadBidding('Woodwind')" class="nav-link px-2">Woodwind</a></li>
                <li class="nav-item"><a href="#" id="Brass"onclick="loadBidding('Brass')" class="nav-link px-2">Brass</a></li>
                <li class="nav-item"><a href="#" id="Stringed" onclick="loadBidding('Stringed')" class="nav-link px-2">Stringed</a></li>
                <li class="nav-item"><a href="#" id="Percussion" onclick="loadBidding('Percussion')" class="nav-link px-2">Percussion</a></li>
            </ul>
            <ul class="navbar-nav">
                <li class="nav-item"><a href="#" onclick="logout()" class="nav-link px-2">Logout</a></li>
            </ul>
          </div>
        </div>
    </div>
</nav>

In the upper-middle part of the body in the 'bidding.html' file there is a label that displays the current auction room title that the user is in, or 'Your bids' if viewing bids the user placed, or 'Your Auctions' if viewing auctions the user created.

This label matches the items displayed in the 'items-area' div found at the bottom of the body. Below the items area div is a button to show more auction items if there are more to show.

'Your Auctions' is the default items view and you can change the contents of the items area view and the current auction label by clicking one of the navigation links.

Inside the body area is also a modal for creating new auctions in the current items view the user is in.

html
<header class="py-3 mb-4 border-bottom">
    <div class="container d-flex flex-wrap">
        <div class="row">
            <div class="col text-left">
                <a href="/" class="d-flex align-items-center mb-3 mb-lg-0 me-lg-auto text-dark text-decoration-none">
                    <svg class="bi me-2" width="40" height="32"><use xlink:href="#bootstrap"/></svg>
                    <span id="cat-label-head"class="fs-4">Your Recent Bids</span>
                </a>
            </div>
            <div class="col-md-auto">
            <div class="modal" id="newAuctionModal" tabindex="-1" role="dialog">
                <div class="modal-dialog modal-dialog-centered" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title">Create New Auction</h5>
                            <button type="button" class="close" data-bs-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                        </div>
                        <div class="modal-body">
                            <p>Item Name</p>
                            <input class="form-control" type="text" class="form-control-plaintext" id="nameInput" onkeydown="limit(this, 30);" placeholder="Item Name">
                            <br>
                            <p>Item Description</p>
                            <textarea class="form-control" id="desInput" type="text" class="form-control-plaintext" onkeydown="limit(this, 200);" placeholder="What is it? Where is it located?"></textarea>
                            <br>
                            <p>Item Image</p>
                            <input class="form-control" type="file" id="imageInput" placeholder="/images/lamp.png">
                            <br>
                            <p>Contact Info (Shared with winner)</p>
                            <input class="form-control" id="contactInput" type="text" class="form-control-plaintext" onkeydown="limit(this, 100);" placeholder="Tell the winner how to contact you.">
                            <br>
                            <p>Shipping Options</p>
                            <div class="form-check">
                                <input class="form-check-input" type="checkbox" value="" id="shipCheckChecked" checked>
                                <label class="form-check-label" for="shipCheckChecked">
                                  I'm willling to ship to buyer
                                </label>
                            </div>
                            <br>
                            <p>Auction Length</p>
                            <select class="form-select" aria-label="auction time" id="time_select">
                                <option selected value="1">1 Hour</option>
                                <option value="2">12 Hours</option>
                                <option value="3">24 Hours</option>
                            </select>
                        </div>
                        <div class="modal-footer">
                            <button type="button" data-bs-dismiss="modal" class="btn btn-secondary">Cancel</button>
                            <button id="newPublish" type="button" onclick="newItem()" class="btn btn-primary">Publish</button>
                        </div>
                    </div>
                </div>
            </div>
            <button data-bs-toggle="modal" id="create_new" data-bs-target="#newAuctionModal" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#newAuctionModal" type="button">Create New Auction</button>
          </div>               
        </div>

    </div>
</header>
<div class="msg-group" id="items-area">                 
</div>
<nav aria-label="page-buttons-nav"  class="container d-flex flex-wrap justify-content-center">
    <ul id="page-buttons" class="pagination hidden">
      <li class="page-item" id="load-more-button">
        <a class="page-link" onclick="loadMore()" style="visibility: hidden;" href="#!">Load More</a>
      </li>
    </ul>
  </nav>

Last, at the bottom of the body, is a label to show the current active online bidders in the bidding channel.

We have a how-to about building an active users counter for any website that'll show you how to pull this functionality into any app.

Aside from bootstrap, the PubNub JavaScript SDK, the Auth0 JavaScript SDK, and the 'bidding.js' file are included to power the features of the application.

html
<span class="badge bg-primary online-users-label" id="online-users-label">Online Bidders: <span id="online-users-count">NA</span></span>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
        <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.7.1.2.min.js"></script>
        <script src="https://cdn.auth0.com/js/auth0-spa-js/1.19/auth0-spa-js.production.js"></script>
        <script src="https://pubnubdevelopers.github.io/Auction-Demo/bidding.js"></script>

Next, let's take a look at how the JavaScript that powers the app works starting with the 'login.js' file.

The 'login.js' file is used by 'index.html' to trigger the Auth0 lock for the user to login. In this file is where you set the keys for your own Auth0 lock. After the user has logged in they are redirected to 'bidding.html' with a unique user id that will be used by the application for the user's identity.

In order to run this demo locally you'll need your own Auth0 keys. Follow the latest Auth0 guide for getting your keys.

Make sure you create a 'Regular Web Application' and set these urls as the 'Allowed Callback URLs', 'Allowed Logout URLs', and 'Allowed Web Origins' to:

http://localhost:3000/, http://localhost:3000/bidding.html, http://localhost:3000/bidding.html/

Set this url as the 'App Login URL':

http://localhost:3000/

Copy your keys into the 'login.js' file.

javascript
let auth0 = null

window.onload = async () => {
  await configureClient()
}

const configureClient = async () => {
  auth0 = await createAuth0Client({
    domain: "YOUR_AUTH_DOMAIN_HERE",
    client_id: "YOUR_AUTH_KEY_HERE",
  })
}

const login = async () => {
  await auth0.loginWithRedirect({
    redirect_uri: "http://localhost:3000/bidding.html",
  })
}

Next, we'll review 'bidding.js' from top to bottom.

At the top there are the variables used to initialize Auth0 and PubNub, and variables to store the results of the auction items to be displayed.

The window.onload method triggers the check for the user's sign in details as well as calls the function that will load the auction items when the page has finished loading.

You'll need to edit your Auth0 client details to match the same details you used in 'login.js' previously.

The makeRandom() function is later used to create random strings for new unique bidding channels for new auction items.

javascript
let auth0 = null;
let sub = null;
let pubnub = null;
let first_result = null;
let current_channel = null;
let itemsArea = null;

window.onload = async () => {
    await configureClient()
    await processLoginState()
    updateUI();
    itemsArea = document.getElementById('items-area');
}

const configureClient = async () => {
    auth0 = await createAuth0Client({
        domain: "YOUR_AUTH_API_DOMAIN_HERE",
        client_id: "YOUR_AUTH_API_KEY_HERE",
    })
}

const processLoginState = async () => {
    // Check code and state parameters
    const query = window.location.search
    if (query.includes("code=") && query.includes("state=")) {
        // Process the login state
        await auth0.handleRedirectCallback()
        // Use replaceState to redirect the user away and remove the querystring parameters
        window.history.replaceState({}, document.title, window.location.pathname)
    }
}

const logout = () => {
    auth0.logout({
        returnTo: "http://localhost:3000/",
    })
}

function makeRandom(length) {
    var result = '';
    var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var charactersLength = characters.length;
    for (var i = 0; i < length; i++) {
        result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
}

The auctionItemControl class takes PubNub messages and formats them as auction items in the auction. These can be new messages (auction items) that were posted in real-time or the history of messages (auction items) posted to a channel. In this way we can use PubNub Channels to store auction items. When we load a bidding room all of the items are able to be loaded and displayed. Each item message contains the auctions items details, the bidding details and end time, and the time when the auction will end.

Each auction item gets it's own countdown. Each of the auction items also has its own bidding channel to manage bids. We subscribe to that bidding channel and make a request to fetch the last message from the auction item's bidding channel. This message will be the last and most recent bid and we display that as well.

The auction item displayed also contains an accordion area that'll be used to place new bids. This area is hidden if the bidding has ended. When bidding has ended only the winner will see an alert with instructions on how to collect the auction item.

Each auction item is appended to the 'items-area' div and displayed.

javascript
class auctionItemControl { // Formats messages and scrolls into view.
    displayItem(uuid, msg_uuid, msg, timetoken) {
        if (!itemsArea.innerHTML.includes(msg.bidding_channel)) {
            itemsArea.innerHTML = this.msg(uuid, msg_uuid, msg, 'center', 'dark', timetoken) + itemsArea.innerHTML
        }
    }
    msg(uuid, msg_uuid, msg, side, style, timetoken) {
        var date = new Date(Math.trunc((timetoken / 10000), 16));
        var timedisplay = date.toLocaleString();
        var new_msg = {};
        new_msg.description = msg.description;
        new_msg.name = msg.name;
        new_msg.end_time = msg.end_time;
        new_msg.bidding_channel = msg.bidding_channel;
        new_msg.image = msg.image;
        new_msg.shipping = msg.shipping;
        new_msg.collect_details = msg.collect_details;

        if (new_msg.description == undefined) {
            new_msg.description = "This is a rare item. Bid on this item as you will never find it ever again. I only have one.";
        }
        if (new_msg.name == undefined) {
            new_msg.name = "Test Item Name";
        }
        if (new_msg.end_time == undefined) {
            new_msg.end_time = new Date().getTime()
        }
        if (new_msg.bidding_channel == undefined) {
            new_msg.bidding_channel = "TestItemName" + new Date().getTime() + 3650000;
        }
        if (new_msg.image == undefined) {
            new_msg.image = "https://pubnubdevelopers.github.io/Auction-Demo/images/lamp.png";
        }
        if (new_msg.shipping == undefined) {
            new_msg.shipping = "Yes";
        }
        if (new_msg.collect_details == undefined) {
            new_msg.collect_details = "Please contact chandler@pubnub.com for payment and shipping. Thank you for buying (:";
        }

        // Countdown
        var x = setInterval(function () {
            if (document.getElementById("countdown-" + new_msg.bidding_channel) != null) {
                // Get today's date and time
                var now = new Date().getTime();
                // Find the distance between now and the count down date
                var distance = new_msg.end_time - now;
                // Time calculations for days, hours, minutes and seconds
                var days = Math.floor(distance / (1000 * 60 * 60 * 24));
                var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
                var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
                var seconds = Math.floor((distance % (1000 * 60)) / 1000);
                // Display the result in the element with id="demo"
                document.getElementById("countdown-" + new_msg.bidding_channel).innerHTML = days + "d " + hours + "h "
                    + minutes + "m " + seconds + "s ";
                // If the count down is finished, write some text
                if (distance < 0) {
                    clearInterval(x);
                    document.getElementById("countdown-" + new_msg.bidding_channel).innerHTML = "Auction Has Ended";
                    document.getElementsByClassName(new_msg.bidding_channel + "-accordionBid")[0].style.visibility = 'hidden';
                    // Get last Bid
                    pubnub.fetchMessages(
                        {
                            channels: [new_msg.bidding_channel],
                            count: 1
                        },
                        function (status, response) {
                            if (response.channels[encodeURIComponent(new_msg.bidding_channel)] && encodeURIComponent(new_msg.bidding_channel) in response.channels) {
                                response.channels[encodeURIComponent(new_msg.bidding_channel)].forEach((message) => {
                                    if (message.uuid == pubnub.getUUID()) { // you won the bid!!
                                        document.getElementById('win-' + new_msg.bidding_channel).style.visibility = 'visible';
                                    }
                                });
                            }
                        });
                }
            } else {
                clearInterval(x);
            }
        }, 1000);

        pubnub.subscribe({ // Subscribe to wait for messages
            channels: [new_msg.bidding_channel],
            withPresence: false
        });

        // Get last Bid
        pubnub.fetchMessages(
            {
                channels: [new_msg.bidding_channel],
                count: 1
            },
            function (status, response) {
                if (response.channels[encodeURIComponent(new_msg.bidding_channel)] && encodeURIComponent(new_msg.bidding_channel) in response.channels) {
                    response.channels[encodeURIComponent(new_msg.bidding_channel)].forEach((message) => {
                        document.getElementById('current-bid-' + new_msg.bidding_channel).innerHTML = formatter.format(message.message);
                    });
                }
            });

        // Delete the next line to fix images
        new_msg.image = "https://pubnubdevelopers.github.io/Auction-Demo/images/lamp.png";
        var new_vote = "x"+makeRandom(5);
        var msgTemp = `
            <div class="card text-white bg-${style}">
                <div class="card-body">
                    <h2 class="card-subtitle mb-2 text-${side}"> <span class="display-${uuid}">${new_msg.name}<span></h2>
                    <div class="text-center">
                        <img src="${new_msg.image}" class="rounded" alt="Image of item for sale">
                    </div>
                    <br>
                    <h3 class="card-subtitle mb-2 text-${side}"><small>Time Left: <span data-endtime="${new_msg.end_time}" data-biddingchannel="${new_msg.bidding_channel}" class="countdowns" id="countdown-${new_msg.bidding_channel}"><span></small></h3>
                    <h3 class="card-subtitle mb-2 text-${side}"><small>Current Bid: <span id="current-bid-${new_msg.bidding_channel}">No Bids Yet<span></small></h3>
                    <h6 class="card-subtitle mb-2 text-${side}"><small>Posted on: ${timedisplay}</small></h6>
                    <h6 class="card-subtitle mb-2 text-${side}"><small>Offers Shipping: ${new_msg.shipping}</small></h6>
                    <p class="card-text text-${side}">${new_msg.description}</p>
                    <div class="alert alert-warning" id="win-${new_msg.bidding_channel}" style="visibility: hidden;" role="alert">
                        <h2 class="card-subtitle mb-2 text-${side}">You won this item!</h2>
                        <h4 class="card-subtitle mb-2 text-${side}"><small>Collection Details: ${new_msg.collect_details}</small></h6>
                    </div>

                    <div class="col text-center">
                        <div class="accordion accordion-flush ${new_msg.bidding_channel}-accordionBid" id="${new_vote}accordionBid">
                            <div class="accordion-item">
                                <h2 class="accordion-header" id="headingPlaceBid">
                                <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#${new_vote}collapseOne" aria-expanded="true" aria-controls="${new_vote}collapseOne">
                                    Place Bid
                                </button>
                                </h2>
                                <div id="${new_vote}collapseOne" class="accordion-collapse collapse" aria-labelledby="headingPlaceBid" data-bs-parent="#${new_vote}accordionBid">
                                    <div class="accordion-body text-dark">
                                        <strong>Read Carefully:</strong> By placing a bid you are making an agreement with the seller to pay for your item. You must work with the seller directly to pay for the item and arrange for shipping or pickup. You will get contact details if you place the winning bid. 
                                        <br>
                                        <br>
                                        <form>
                                            <div class="mb-3">
                                                <h5><label for="submitBid" class="form-label">Your Bid:</label></h5>
                                                <input id="${new_msg.bidding_channel}-bid-input" type="number" min="0.01" step="0.01"  class="form-control" id="submitBid">
                                            </div>
                                            <button type="button" id="${new_msg.bidding_channel}-bid" onclick="placeBid('${new_msg.bidding_channel}', '${new_msg.name}','${new_msg.description}', '${new_msg.image}', '${new_msg.collect_details}', '${new_msg.shipping}', '${new_msg.end_time}')" class="btn btn-primary">Place Bid</button>
                                        </form>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            `;
        return msgTemp;
    }
}

var auction_item = new auctionItemControl();

The placeBid() function is called when a user has inputted a new bid for an auction item and submitted the bid. In this demo the bid is compared to the last bid for the auction item and if it is greater the bid message is published.

This same comparison logic can and should be added to a PubNub Function to ensure there's no way a user can submit a score that's not approved. In this demo we kept it simple and do this comparison client side. With PubNub Functions you can block users from placing bids or have other restrictions unique to your app.

The loadMore() function is used when there are too many auction items to display. This function fetches more auction items 10 at a time when the 'Load More' button is clicked.

javascript
function placeBid(new_msg_bidding_channel, new_msg_name, new_msg_description, new_msg_image, new_msg_collect_details, new_msg_shipping, new_msg_end_time) {
    // Get last Bid
    pubnub.fetchMessages(
        {
            channels: [new_msg_bidding_channel],
            count: 1
        },
        function (status, response) {
            if (response.channels[encodeURIComponent(new_msg_bidding_channel)] && encodeURIComponent(new_msg_bidding_channel) in response.channels) {
                response.channels[encodeURIComponent(new_msg_bidding_channel)].forEach((message) => {
                    var last_bid = message.message;
                    var new_bid = document.getElementById(new_msg_bidding_channel + '-bid-input').value;
                    if (parseInt(new_bid) > parseInt(last_bid)) {
                        pubnub.publish({
                            message: new_bid,
                            channel: new_msg_bidding_channel,
                        }, (status, response) => {
                            // handle status, response
                            //console.log(response);
                        });
                        // Add to your-bids
                        let new_auction_item = {
                            name: new_msg_name,
                            description: new_msg_description,
                            image: new_msg_image,
                            collect_details: new_msg_collect_details,
                            shipping: new_msg_shipping,
                            end_time: new_msg_end_time,
                            bidding_channel: new_msg_bidding_channel
                        }
                        pubnub.publish({
                            message: new_auction_item,
                            channel: sub,
                        }, (status, response) => {
                            // handle status, response
                            // console.log(response);
                        });
                        actionCompleted({action: 'Place A Bid', windowLocation: orgwindowlocation}); // This is for the interactive demo on PubNub.com. It is not part of the demo of this application. 
                        alert("Your bid has been placed.")
                    } else {
                        alert("Your new bid must be higher than the previous bids.")
                    }
                });
            }
        });
}

function loadMore() {
    document.getElementById('page-buttons').style.visibility = 'hidden';
    pubnub.fetchMessages( // Get the last 10 items
        {
            channels: [current_channel],
            count: 11,
            reverse: false,
            start: first_result
        },
        function (status, response) {
            let message_count = 0;
            if (response.channels[encodeURIComponent(current_channel)] && encodeURIComponent(current_channel) in response.channels) {
                response.channels[encodeURIComponent(current_channel)].forEach((message) => {
                    message_count = message_count + 1;
                    if (message_count == 2) {
                        first_result = message.timetoken;
                    }
                    if (message_count > 10) {
                        document.getElementById('page-buttons').style.visibility = 'visible';
                    }
                    auction_item.displayItem(pubnub.getUUID(), message.uuid, message, message.timetoken);
                });
            }
            if (document.getElementById('items-area').childNodes.length === 0) {
                document.getElementById('items-area').innerHTML = "Nothing here to show. Try creating a new auction or placing a bid."
            } 
        }
    );
    return false;
}

The newItem() function takes input from the Create New Auction modal and publishes the new auction item to start bidding. This function ensures all of the needed data is present, publishes the auction details to the ‘Your Auctions’ channel, publishes the auction details to the current auction room channel, and publishes a ‘0’ bid to the auction items bidding channel.

javascript
function newItem() {
    let new_end_time = new Date();
    if (document.getElementById("time_select").value == 1) {
        new_end_time = new Date(new_end_time.getTime() + 3600000);
    } else if (document.getElementById("time_select").value == 2) {
        new_end_time = new Date(new_end_time.getTime() + 43200000);
    } else if (document.getElementById("time_select").value == 3) {
        new_end_time = new Date(new_end_time.getTime() + 86400000);
    }
    let set_end_time = new_end_time.getTime().toString();
    let will_ship = "No";
    if (document.getElementById('shipCheckChecked').checked) {
        will_ship = "Yes";
    }
    let new_bidding_channel = makeRandom(15) + "_" + set_end_time;
    let new_auction_item = {
        name: document.getElementById('nameInput').value,
        description: document.getElementById('desInput').value,
        //image: document.getElementById('imageInput').value,
        image: "https://pubnubdevelopers.github.io/Auction-Demo/images/lamp.png",
        collect_details: document.getElementById('contactInput').value,
        shipping: will_ship,
        end_time: set_end_time,
        bidding_channel: new_bidding_channel
    }
    pubnub.publish({
        message: new_auction_item,
        channel: current_channel,
    }, (status, response) => {
        // handle status, response
        console.log(response);
    });
    pubnub.publish({
        message: new_auction_item,
        channel: sub + "_auctions",
    }, (status, response) => {
        // console.log(response);
        // handle status, response
    });
    pubnub.publish({
        message: "0",
        channel: new_bidding_channel,
    }, (status, response) => {
        // handle status, response
        //console.log(response);
    });
    actionCompleted({action: 'Create An Auction', windowLocation: orgwindowlocation}); // This is for the interactive demo on PubNub.com. It is not part of the demo of this application. 

    alert("New auction has been posted.");
    document.getElementById('contactInput').value = "";
    document.getElementById('nameInput').value = "";
    document.getElementById('imageInput').value = "";
    document.getElementById('desInput').value = "";
    loadBidding(current_channel); // Reload view
}

The loadBidding() function reloads the items when when the page is first loaded and whenever the auction room is changed.

The loadBidding() function subscribes to the current auction room to automatically add new auctions as they are created and update bidding as bids are received.

The 'online-users-count' label is updated with the current count of active participants and updated when Presence events occur.

Then the last 10 auction items posted to the current action room are loaded from Message Persistence and displayed.

javascript
function loadBidding(channel) {
    itemsArea.innerHTML = "";
    pubnub.unsubscribeAll();
    current_channel = channel;
    if (channel == "your-bids") {
        current_channel = sub;
        setActive("your-bids");
        document.getElementById('cat-label-head').innerHTML = "Your Recent Bids";
        document.getElementById('create_new').style.visibility = 'hidden';
        document.getElementById('online-users-label').style.visibility = 'hidden';
    } else if (channel == "auctions") { // If loading your bids or auctions then hide the create new auction button
        setActive("auctions");
        actionCompleted({action: 'View Your Auctions', windowLocation: orgwindowlocation}); // This is for the interactive demo on PubNub.com. It is not part of the demo of this application. 
        current_channel = sub + "_auctions";
        document.getElementById('cat-label-head').innerHTML = "Your Auctions";
        document.getElementById('create_new').style.visibility = 'hidden';
        document.getElementById('online-users-label').style.visibility = 'hidden';
    } else {
        setActive(channel);
        actionCompleted({action: 'Change Category', windowLocation: orgwindowlocation}); // This is for the interactive demo on PubNub.com. It is not part of the demo of this application. 
        document.getElementById('cat-label-head').innerHTML = channel;
        document.getElementById('create_new').style.visibility = 'visible';
        document.getElementById('online-users-label').style.visibility = 'visible';
        pubnub.hereNow({ // Update the number of online members.
            channels: [channel],
        }).then((response) => {
            if (response.totalOccupancy == 0) {
                document.getElementById("online-users-count").innerHTML = "1";
            } else {
                document.getElementById("online-users-count").innerHTML = response.totalOccupancy;
            }
        }).catch((error) => {
            // console.log(error)
        });
        pubnub.subscribe({ // Subscribe to wait for messages
            channels: [channel],
            withPresence: true
        });
    }

    pubnub.addListener({ // Get new messages.
        presence: (presenceEvent) => { // Update the number of online members.
            if (presenceEvent.occupancy == 0) {
                document.getElementById("online-users-count").innerHTML = "1";
            } else {
                document.getElementById("online-users-count").innerHTML = presenceEvent.occupancy;
            }
        },
        message: function (msg) {
            // console.log(msg);
            document.getElementById("current-bid-" + msg.channel).innerHTML = formatter.format(msg.message);
        }
    });

    document.getElementById('page-buttons').style.visibility = 'hidden';
    pubnub.fetchMessages( // Get the last 10 items
        {
            channels: [current_channel],
            count: 11,
            reverse: false,
        },
        function (status, response) {
            let message_count = 0;
            if (response.channels[encodeURIComponent(current_channel)] && encodeURIComponent(current_channel) in response.channels) {
                response.channels[encodeURIComponent(current_channel)].forEach((message) => {
                    message_count = message_count + 1;
                    if (message_count == 2) {
                        first_result = message.timetoken;
                    }
                    if (message_count > 10) {
                        document.getElementById('page-buttons').style.visibility = 'visible';
                    }
                    auction_item.displayItem(pubnub.getUUID(), message.uuid, message.message, message.timetoken);
                });
            }
            if (document.getElementById('items-area').childNodes.length === 0) {
                document.getElementById('items-area').innerHTML = "Nothing here to show. Try creating a new auction or placing a bid."
            } 
        }
    );
}

The updateUI() function is called only once when the page first loads. It checks Auth0 for the user’s authentication and redirects back to ‘login.html’ if the user’s session has expired.

If the user is authenticated we use the user’s unique subscriber ID from Auth0 to identify the user to PubNub. We can then initialize PubNub with the Publish and Subscribe keys from the PubNub dashboard.

Replace these keys with your own PubNub API keys from the previous step in this tutorial.

Then we call the loadBidding() function to get the auction items to display.

javascript
const updateUI = async () => {
    const isAuthenticated = await auth0.isAuthenticated()
    if (!isAuthenticated) {
        window.location.replace("http://localhost:3000");
    } else {
        const userdetails = await auth0.getUser();
        // console.log("User ID: " + userdetails.sub);
        pubnub = new PubNub({ // Set your PubNub keys here 
            publishKey: 'YOUR-PUB-KEY-HERE',
            subscribeKey: 'YOUR-SUB-KEY-HERE',
            uuid: userdetails.sub
        });
        sub = userdetails.sub;
        loadBidding("your-bids");
    }
}

Now you're ready to run the Real-Time Auction / Marketplace demo locally with your own Auth0 API Keys and PubNub API Keys.

There are several ways to run this app locally. The easiest is a simple local testing server using Python.

Open your terminal and navigate to the project folder. Then run this command:

shell
python -m SimpleHTTPServer 3000
Step 04

Congratulations - what's next?

More Resources for Building Real-time Solutions

PubNub Functions BasicsHow-to Build a Real-Time Active User CounterDeveloper Path Collection

Ready to get beyond Proof of Concept?

Schedule a 15 minute chat with one of our experts to get your project moving

Talk to an Expert
Keep building

Take it to production on your own keys

Create a free PubNub account to get permanent keys for this app.