NEAR Tutorial, Part 2

Set up some boiler plate code

This is going to be a little longer than the last two tutorials because now we're going to get into some front end code. Since we're making HTML5 games we're going to stick with vanilla Javascript

This is what we're going to make: Hello game

Here's some starter code:

<!doctype html>
            <html>
                    <head><meta charset="utf-8" />
                        <!-- near-api-js -->
                        <script src="https://cdn.jsdelivr.net/npm/near-api-js/dist/near-api-js.min.js"></script>
                        </script>
                        <!-- Styles, optionally you can have a styles.css file for these. -->
                        <style>
                        /* These are my styles you can change to suit youself */
                          body{
                            background: #a29bfe;
                          }
                          h1,h2{
                            color: #dfe6e9;
                          }
                          canvas{
                            /*If you don't like pink, go ahead and change it.*/
                            background: #ff7675; 
                            width:300px;
                            height:300px;
                          }
                          .after-sign-in{
                            text-align: center;
                          }
                        </style>
                  </head> 
            <body style="margin:0;overflow: hidden;">
                <div class="container">
                    <!-- What to show before sign in -->
                    <div class="sign-in" style="display: none;"></div>
                    <!-- What to show after sign in -->
                    <div class="after-sign-in" style="display: none;">
                        <!-- Our HTML5 canvas where all our cool game stuff goes -->
                        <canvas id=canvas>   
                        </canvas>
                    </div>
                </div>
            </body>
            </html>
            
            <!-- Our JavaScript code goes here, if you prefer you can put it in a scripts.js file -->
            <script>
            </script>

First thing you'll notice about this starter code is that we have a div called sign in and a div called after-sign-in, this is to control what is seen based on if the user has logged in with testnet.

So let's write some code inside the script tag to control that:

First we need our CONTRACT_NAME

Make sure you use the development contract ID you found in the neardev folder.


                var CONTRACT_NAME='dev-Somelongnumber';
            

Next we'll drop in some boiler plate code to connect a wallet, I'm going to combine a few things here, but if you'd like a breakdown check out the NEAR-API-JS Quick Reference guide


        async function connect() {
        // Connect to nearApi
        let near = await nearApi.connect({
        nodeUrl: "https://rpc.testnet.near.org",
        walletUrl: "https://wallet.testnet.near.org",
        helperUrl: "https://helper.testnet.near.org",
        explorerUrl: "https://explorer.testnet.near.org",
        networkId: 'testnet', // We are using testnet
        keyStore: new window.nearApi.keyStores.BrowserLocalStorageKeyStore(window.localStorage, 'Example App')
        });

        // Connect to user's wallet
        window.walletConnection = new nearApi.WalletConnection(near);
        let account;
        if (window.walletConnection.isSignedIn()) {
            // Logged in account, can write as user signed up through wallet
            account = walletConnection.account();
            // connect to a NEAR smart contract
            window.contract = new nearApi.Contract(account, CONTRACT_NAME, {
            viewMethods: [],
            changeMethods: []
            });
        } else {
            // Contract account, normally only gonna work in read only mode
            account = new nearApi.Account(near.connection, CONTRACT_NAME);
        }
    }
                

Let's add right after that some code to call connect, update our page and catch any errors that get returned.


        nearApi.nearInitPromise = connect()
        .then(updateUI)
        .catch(console.error);
    

Now we need that updateUI fuction...


      // switch between logged in/out
      function updateUI() {
          if (!window.walletConnection.getAccountId()) {
          // If you're not logged in show sign-in div
          Array.from(document.querySelectorAll('.sign-in')).map(it => it.style =
           'display: block;margin-left: 10%;margin-top: 10%;');
           // If you signed out reset to not visible
           Array.from(document.querySelectorAll('.after-sign-in')).map(it => it.style = 
          'display: none;');
  
      }else {
          // If you're are logged in show after-sign-in div
          Array.from(document.querySelectorAll('.after-sign-in')).map(it => it.style = 
          'display: block;');
          ifLoggedIn();
      }
      }
    

So if you're following my logic you see inside the updateUI that function 'ifLoggedIn()', so let's start filling that in.


    // Things I want to do if logged in
    function ifLoggedIn(){
      // Get username from nearApi without using the contract. 
      var userName = window.walletConnection.getAccountId();
      // Format the username to get rid of .testnet
      userName = userName.slice(0,userName.lastIndexOf('.'));
    
    }

We're also going to want to add a logout button event listener inside our 'ifLoggedIn()' function:


    //Logout
    document.querySelector('.sign-out').addEventListener('click', () => {
      if (window.walletConnection.isSignedIn()) {
        window.walletConnection.signOut();
        updateUI();
      }
    });
    

And a login button event listener outside our 'ifLoggedIn()' function:


    // Log in user using NEAR Wallet on "Sign In" button click
    document.querySelector('.sign-in').addEventListener('click', () => {
    window.walletConnection.requestSignIn(CONTRACT_NAME, 'Example App');
    });
    

Let's take a minute to go back to the html and add the login/logout buttons, and also some headings for both the sign-in and after-sign-in divs:

Sign-in:

<h1>Welcome!</h1>
        <h2>Sign in with NEAR to say hello!</h2>
        <button class="sign-in btn" style="background-color: #0072CE;">Sign In</button>
    

After-sign-in:

        <h1>How would you like to be greeted?</h1>
        <input id="text" placeholder="Greeting"></input>
        <button id="set-greeting">Change Greeting</button>
        <button class="sign-out btn" style="background-color: #0072CE;">Sign Out</button>
        <h2 id="response">Loading response...</h2>

Before we continue to the next section, this is a good time to point out that you will likely need to set up a local testing server, you may already have something you prefer, but here's a quick solution using python: How do you set up a local testing server?

With what we have now, if you run open the file using on local host you should be able to login/logout

Calling the contract

Now we need to add the methods in our contract, if you go inside the assembly folder in myFirstApp that you set up you'll see two methods: setGreeting and getGreeting, we need to add those into our async connect() function like this:

        
        window.contract = new nearApi.Contract(account, CONTRACT_NAME, {
            viewMethods: ['getGreeting'],
            changeMethods: ['setGreeting']
        });
        

View methods don't change anything, but change methods change what's stored on the blockchain.

You can drop a click listener inside your ifLoggedIn() function:


        document.getElementById('set-greeting').addEventListener('click', () => {
            if (window.walletConnection.isSignedIn()) {
              changeHello();
            }
          });
        

So now we have a button that will call a function changeHello(), let's write that function to call our contract method setGreeting:

    
          // async function to call a change function setGreeting() 
          async function changeHello(){
              // call the contract function
              let result = await window.contract.setGreeting({
                 message: document.getElementById('text').value
                  });
                  console.log(result);
                  // clear the form
                  document.getElementById('text'),innerHTML="";
              return;
          }

Let's write another async function to call our view function getGreeting


            // async function to call contract function getGreeting() and change innerHTML
            async function viewHello(){
                // call the contract function
                let result = await window.contract.getGreeting({
                      accountId: window.walletConnection.getAccountId() }
                    );
            
                // update html with greeting
                document.getElementById('response').innerHTML=result;
                return;
            }               
          

Let's call that function inside our ifLoggedIn() function so that we have a greeting to put inside our canvas.


            // call the view method to update the greeting on the html page.
            viewHello();
          

Displaying our greeting within our canvas

Now we need to add some canvas code inside the ifLoggedIn() function bellow the other code.


          // ###### Canvas Code #########
          var c = canvas.getContext`2d`;
          canvas.width = 300; 
          canvas.height =300;
          c.w = canvas.height;
        c.h = canvas.width;
      
          // DRAW LOOP
          let reqAnimationId;
          function smoothAnimation(e) {
              canvas.width = 300, canvas.height = 300;
              c.w=canvas.width, c.h = canvas.height;
              animate();
              reqAnimationId = requestAnimationFrame(smoothAnimation)
          }
          // to start
          function start() {
              reqAnimationId = requestAnimationFrame(smoothAnimation)
          }
          function animate(){
              print(document.getElementById('response').innerHTML, c.w/2, c.h/2, 1, "#FFF");
          }
      
          start();
      
          // Canvas print util by vertfromage
          function print(text, width, height, fontScale, color) {
          c.textAlign = 'center';
          c.fillStyle = color;
          c.font = fontScale+ 'vw Consolas';
          c.fillText(text, width, height);
          }
          // ########## END Canvas Code ################
        

We don't really need our response header to be visible outside our div, so let's make that invisible


        #response{
          visibility: hidden;
        }

But of course we're talking about html5 games here we don't want everything to be tied to document.getElementById, so how should we set that up with html5 variables?

Easy we save our response in a variable called promise, because that's what our async function is going to return, but we can't access the actual string until the promise is completed, so we use a .then() function like so:

    
          // call the view method to update the greeting on the html page.
          let promise = viewHello();
          var greetingText = "Loading response...";
      
          promise.then(
                  function(value) {
                      greetingText = value;
                  });
          

and update your print() code, comment out or delete the document.getElementByID line

        
          // let greetingText = document.getElementById('response').innerHTML+", "+userName;
          print(greetingText+", "+userName, c.w/2, c.h/2, 2, "#FFF");

Also update your viewHello() function so that it returns the result rather than adding it to the innerHTML

            
          // async function to call contract function getGreeting() and change innerHTML
          async function viewHello(){
              // call the contract function
              let result = await window.contract.getGreeting({
                    accountId: window.walletConnection.getAccountId() }
                  );
          
              // // update html with greeting
              // document.getElementById('response').innerHTML=result;
              return result;
          }

Finally we have a very little fussy thing... when we call changeHello() it doesn't also call viewHello()...

The fix here is in our onClick we can chain the two functions together like so,


            // set-greeting
            document.getElementById('set-greeting').addEventListener('click', () => {
                    if (window.walletConnection.isSignedIn()) {
                      greetingText = "Processing...";
                      changeHello().then(
                          function(){
                            let promise = viewHello();
        
                            promise.then(
                                    function(value) {
                                        greetingText = value;
                                    });
                          }
                      )
                    }
                  });
          

The entire code

This is the end of the tutorial, so here's all the code together incase something goes wrong:


          <!doctype html>
<html>
		<head><meta charset="utf-8" />
            <!-- near-api-js -->
            <script src="https://cdn.jsdelivr.net/npm/near-api-js/dist/near-api-js.min.js"></script>
            </script>
            <!-- Styles, optionally you can have a styles.css file for these. -->
            <style>
            /* These are my styles you can change to suit youself */
              body{
                background: #a29bfe;
              }
              h1,h2{
                color: #dfe6e9;
              }
              canvas{
                /*If you don't like pink, go ahead and change it.*/
                background: #ff7675; 
              }
              .after-sign-in{
                text-align: center;
              }
              #response{
                visibility: hidden;
              }
            </style>
      </head> 
<body style="margin:0;overflow: hidden;">
    <div class="container">
        <!-- What to show before sign in -->
        <div class="sign-in" style="display: none;">
            <h1>Welcome!</h1>
            <h2>Sign in with NEAR to say hello!</h2>
            <button class="sign-in btn" style="background-color: #0072CE;">Sign In</button>
        </div>
        <!-- What to show after sign in -->
        <div class="after-sign-in" style="display: none;">
            <h1>Fill in your name for a little suprise greeting!</h1>
            <textarea id="text" placeholder="Name"></textarea><br>
            <button id="send-greeting">Send Greeting</button>
            <button class="sign-out btn" style="background-color: #0072CE;">Sign Out</button>
            <h2 id="response">Loading response...</h2>
            <!-- Our HTML5 canvas where all our cool game stuff goes -->
            <canvas id=canvas>   
            </canvas>
        </div>
    </div>
</body>
</html>

<!-- Our JavaScript code goes here, if you prefer you can put it in a scripts.js file -->
<script>
    var CONTRACT_NAME='dev-1638464780305-36276259815103';

    
    async function connect() {
    // Connect to nearApi
    let near = await nearApi.connect({
    nodeUrl: "https://rpc.testnet.near.org",
    walletUrl: "https://wallet.testnet.near.org",
    helperUrl: "https://helper.testnet.near.org",
    explorerUrl: "https://explorer.testnet.near.org",
    networkId: 'testnet', // We are using testnet
    keyStore: new window.nearApi.keyStores.BrowserLocalStorageKeyStore(window.localStorage, 'your key')
    });

    // Connect to their wallet
    window.walletConnection = new nearApi.WalletConnection(near);
    let account;
    if (window.walletConnection.isSignedIn()) {
        // Logged in account, can write as user signed up through wallet
        account = walletConnection.account();
        // connect to a NEAR smart contract
        window.contract = new nearApi.Contract(account, CONTRACT_NAME, {
            viewMethods: ['getGreeting'],
            changeMethods: ['setGreeting']
        });
    } else {
        // Contract account, normally only gonna work in read only mode
        account = new nearApi.Account(near.connection, CONTRACT_NAME);
    }
}

// Log in user using NEAR Wallet on "Sign In" button click
document.querySelector('.sign-in').addEventListener('click', () => {
  window.walletConnection.requestSignIn(CONTRACT_NAME, 'Nearvember');
});

nearApi.nearInitPromise = connect()
  .then(updateUI)
  .catch(console.error);


// switch between logged in/out
function updateUI() {
    if (!window.walletConnection.getAccountId()) {
    // If you're not logged in show sign-in div
    Array.from(document.querySelectorAll('.sign-in')).map(it => 
    it.style = 'display: block;margin-left: 10%;margin-top: 10%;');
  }else {
    // If you're are logged in show after-sign-in div
    Array.from(document.querySelectorAll('.after-sign-in')).map(it =>
     it.style = 'display: block;');
    ifLoggedIn();
  }
}

// Things I want to do if logged in
function ifLoggedIn(){
      // Get username from nearApi without using the contract. 
      var userName = window.walletConnection.getAccountId();
      // Format the username to get rid of .testnet
      userName = userName.slice(0,userName.lastIndexOf('.'));

      document.getElementById('send-greeting').addEventListener('click', () => {
      if (window.walletConnection.isSignedIn()) {
        callHello();
      }
    });

    // async function to call contract
async function callHello(){
    // call the contract
    let contract = await window.contract.set_name({
          args: { name: document.getElementById('text').value }
        })

    // clear the form
    document.getElementById('text'),innerHTML="";
    // Output from contract
    document.getElementById('response').innerHTML=contract;
    return;
}

    //Logout
    document.querySelector('.sign-out').addEventListener('click', () => {
      if (window.walletConnection.isSignedIn()) {
        window.walletConnection.signOut();
        updateUI();
      }
    });
}
</script>
        

Next tutorial

Next we will...

NEAR tutorial 3