Adding a stylesheet

Cascading stylesheets represent one of the most common ways to customize the user experience for an application. This tutorial illustrates how to add a stylesheet when you use React to create a new front-end for your project. If you already know how to add cascading stylesheets (CSS) to a React-based project, you can skip this tutorial.

Currently, you can only use Javascript to implement the front-end for your canister. This tutorial illustrates using the React framework to manage the Document Object Model (DOM) for your canister. Because React has its own custom DOM syntax, you need to modify the webpack configuration to compile the front-end code, which is written in JSX. For more information about learning to use React and JSX, see Getting started on the React website.

Create a new project

To create a new project directory for your custom front-end application:

  1. Open a terminal shell on your local computer, if you don’t already have one open.

  2. Change to the folder you are using for your Internet Computer sample projects.

  3. Verify that you have node.js installed locally, if necessary.

  4. Create a new project by running the following command:

    dfx new contacts
  5. Change to your project directory by running the following command:

    cd contacts

Install the React framework

if you’ve never used React before, you might want to explore the Intro to React tutorial or the React website before editing the front-end code.

To install required framework modules:

  1. Install the React module by running the following command:

    npm install --save react react-dom
  2. Install the required language compiler loaders by running the following command:

    npm install --save typescript ts-loader
  3. Install the required style loaders by running the following command:

    npm install --save style-loader css-loader

Modify the default configuration

To modify settings in the default dfx.json configuration file:

  1. Open the dfx.json configuration file in a text editor.

  2. Change the entry file name to index.jsx to enable adding HTML directly inside Javascript as a template.

  3. Change the name of the main program file to contacts.mo.

  4. Save your changes and close the dfx.json file to continue.

  5. Change to the src/contacts directory.

    cd src/contacts
  6. Rename the main.mo file as contacts.mo by running the following command:

    mv main.mo contacts.mo
  7. Open the contacts.mo file in a text editor and delete the existing content.

  8. Copy and paste the following sample code into the file:

    import List "mo:stdlib/list";
    import AssocList "mo:stdlib/assocList";
    
    type Name = Text;
    type Phone = Nat;
    
    type Entry = {
        name: Name;
        address1: Text;
        address2: Text;
        email: Text;
        phone: Phone;
    };
    
    type ContactsMap = AssocList.AssocList<Name, Entry>;
    
    actor {
        var contact: ContactsMap = List.nil<(Name, Entry)>();
    
        func nameEq(lhs: Name, rhs: Name): Bool {
            return lhs == rhs;
        };
    
        public func insert(name0: Name, address10: Text, address20: Text, email0: Text, phone0: Phone): async () {
            let newEntry : Entry = {
                name = name0;
                address1 = address10;
                address2 = address20;
                email = email0;
                phone = phone0;
            };
    
            let (newContacts, _) = AssocList.replace<Name, Entry>(
                contact,
                name0,
                func(n: Name, m: Name) = n == m,
                ?newEntry
            );
            contact := newContacts;
        };
    
        public query func lookup(name: Name): async ?Entry {
            return AssocList.find<Name, Entry>(contact, name, nameEq);
        };
    };
  9. Save your changes and close the contacts.mo file to continue.

Modify the front-end files

You are now ready to create a new front-end for the default program.

  1. Navigate back to the root of your project directory.

  2. Open the webpack configuration file (webpack.config.js) in a text editor to add the following module key above the plugins section:

    module: {
      rules: [
        { test: /\.(js|ts)x?$/, loader: "ts-loader" },
        { test: /\.css$/, use: ['style-loader','css-loader'] }
      ]
    },

    These settings enable your program to use the ts-loader compiler and to import CSS files.

  3. Save your changes and close the webpack.config.js file to continue.

  4. Create a new file named tsconfig.json, open the file in a text editor, then copy and paste the following into the file:

    {
        "compilerOptions": {
          "target": "es2018",        /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
          "module": "commonjs",      /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
          "lib": ["ES2018", "DOM"],  /* Specify library files to be included in the compilation. */
          "allowJs": true,           /* Allow javascript files to be compiled. */
          "jsx": "react",            /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
        }
    }
  5. Save your changes and close the tsconfig.json file to continue.

Add a stylesheet to your project

You are now ready to create a new cascading stylesheet and add it to your project.

To add a stylesheet:

  1. Change to the src/contacts/public directory.

    cd src/contacts/public
  2. Create a new file named mycontacts.css and open the file in a text editor.

  3. Define some style properties for the front-end.

    For example, copy and paste the following sample styles into the file:

    html {
        background-color: bisque;
    }
    
    body {
        font-family: Arial, Helvetica, sans-serif;
        display: block;
        margin: 10px;
    }
    
    h1 {
        color: darkblue;
        font-size: 32px;
    }
    
    div.new-entry {
        margin: 30px 20px 30px 20px;
    }
    
    .new-entry > div {
        margin-bottom: 15px;
    }
    
    table {
        margin-top: 12px;
        border-top: 1px solid darkblue;
        border-bottom: 1px solid darkblue;
    }
    
    #form {
        margin: 30px 0 30px 20px;
    }
    
    button {
        line-height: 20px;
    }
    
    #lookupName {
        margin-right: 12px;
    }
  4. Save your changes and close the mycontacts.css file to continue.

  5. Open the default index.js file in a text editor and delete the existing content.

  6. Copy and paste the following sample code into the index.js file:

    import contact from 'ic:canisters/contacts';
    import * as React from 'react';
    import { render } from 'react-dom';
    
    import './mycontacts.css'; // Import custom styles
    
    class Contact extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
        };
      }
    
      async doInsert() {
        let name = document.getElementById("newEntryName").value;
        let add1 = document.getElementById("newEntryAddress1").value;
        let add2 = document.getElementById("newEntryAddress2").value;
        let email = document.getElementById("newEntryEmail").value;
        let phone = document.getElementById("newEntryPhone").value;
    
        contact.insert(name, add1, add2, email, parseInt(phone, 10));
      }
    
      async lookup() {
        let name = document.getElementById("lookupName").value;
        contact.lookup(name).then(opt_entry => {
          let [entry] = opt_entry;
    
          if (entry.length == 0) {
            entry = { name: "", description: "", phone: ""};
          } else {
            entry = entry[0]
          }
    
          document.getElementById("newEntryName").value = entry.name;
          document.getElementById("newEntryAddress1").value = entry.address1;
          document.getElementById("newEntryAddress2").value = entry.address2;
          document.getElementById("newEntryEmail").value = entry.email;
          document.getElementById("newEntryPhone").value = entry.phone.toString();
        });
      }
    
      render() {
        return (
          <div class="new-entry">
            <h1>My Contacts</h1>
            <div>
              Add or update contact information:
    	      <form id="contact">
              <table>
                <tr><td>Name:</td><td><input id="newEntryName"></input></td></tr>
                <tr><td>Address 1 (street):</td><td><input id="newEntryAddress1"></input></td></tr>
                <tr><td>Address 2 (city and state):</td><td><input id="newEntryAddress2"></input></td></tr>
                <tr><td>Email:</td><td><input id="newEntryEmail"></input></td></tr>
                <tr><td>Phone:</td><td><input id="newEntryPhone" type="number"></input></td></tr>
              </table>
            </form>
            </div>
            <div>
              <button onClick={() => this.doInsert()}>Add Contact</button>
            </div>
            <div>
              Lookup name: <input id="lookupName" style={{ "line-height": "20px" }}></input>
              <button onClick={() => this.lookup()}>Lookup</button>
            </div>
          </div>
        );
      }
    }
    
    document.title = "DFINITY CONTACT EXAMPLE";
    
    render(<Contact />, document.getElementById('app'));
  7. Rename the modified index.js file as index.jsx by running the following command:

    mv index.js index.jsx

Build the program

You are now ready to compile the program with the new front-end as part of the WebAssembly module.

To build the program with the new front-end:

  1. Open a terminal shell on your local computer, if you don’t already have one open.

  2. Navigate to the root of your contacts project folder.

    Note that this step is required because you must run the dfx build command from within the project directory structure.

  3. Build the executable canister by running the following command:

    dfx build

    The command displays output indicating that the build is successful.

    Building canister contacts
    Building frontend
    Bundling frontend assets in the canister

Start the local network and deploy

You now have a program that can be deployed on your local client network.

To deploy the program on your local network:

  1. Open a terminal shell on your local computer, if you don’t already have one open, and navigate to your contacts project directory.

  2. Start the Internet Computer network on your local computer by running the following command:

    dfx start
  3. Deploy your contacts project on the local network by running the following command:

    dfx canister install --all

    The command output displays output similar to the following:

    Installing code for canister contacts, with canister_id ic:19F69036D7308B0DEE

Viewing the front-end

  1. Open a browser and navigate to the address and port number specified in the dfx.json configuration file.

    By default, the URL uses the localhost address (127.0.0.1) and port number 8000, so you can navigate to the front-end for the hello_world program using 127.0.0.1:8000 for the URL.

    In addition to the host and port, the URL you specify needs to include canisterId parameter to identify the canister that you want the web server to display. To specify the canister, append the identifier using the following syntax:

    ?canisterId=ic:canister_identifier

    For example, the full URL should look similar to the following but with the ic:_canister_identifier_ that was returned by the dfx canister install command:

    http://localhost:8000/?canisterId=ic:81DDA04F69F40FEEAC
    You must use the case-sensitive canisterId parameter before the canister identifier.
  2. Verify that you are prompted with a My Contacts form.

    For example:

    Sample front-end

  3. Create one or more test records by entering text in the Name, Address, and Email input fields and a number in the Phone input field, then clicking Add Contact.

  4. Clear the form fields and type a contact name in the Lookup name field, then click Lookup to see the stored contact information.

    Keep in mind that the Lookup name you type must be an exact match for the name of a contact you added.

Modify the stylesheet and test your changes

After viewing the Contacts application, you might want to make some changes.

To change stylesheet properties:

  1. Open the mycontacts.css file in a text editor and modify its style settings.

    For example, you might want to change the background color or style the input form.

  2. Rebuild the project with your changes by running the following command.

    dfx build
  3. Deploy your project changes and note your new canister identifier.

    dfx canister install --all
  4. View the results in the browser by changing the URL to use the new canister identifier.

  5. Stop the Internet Computer processes running on your local computer by running the following command:

    dfx stop

Modify the front-end or back-end code

If you want to explore further, you might want to experiment with modifying the front-end or back-end code for this tutorial. For example, you might want try modifying the tutorial to do the following:

  • Change the front-end code to clear the input fields after adding a new contact.

  • Change the Motoko program functions to do partial instead of exact string matching on the Name field.

  • Change the Motoko program to allow lookups based on a different field.