Skip to content

Instantly share code, notes, and snippets.

@gabeabrams
Last active May 15, 2019 13:14
Show Gist options
  • Save gabeabrams/1b6e4d312c85e939a446203323387e5c to your computer and use it in GitHub Desktop.
Save gabeabrams/1b6e4d312c85e939a446203323387e5c to your computer and use it in GitHub Desktop.
Harvard IT Summit - Effortlessly Build LTI Apps with DCE’s Canvas Library - Live Demo Notes

IT Summit Programming Outline

Code:

The finished code from our session can be found here:

https://github.com/gabeabrams/imhere-itsummit-2019

Before the session:

  1. Clone a git repo, initialize it as an npm project using npm init
  2. Clean out sandbox course
  3. Add LTI button to sandbox course so when we deploy, we can launch immediately
  4. Reserve a Heroku app and leave it unchanged

During session:

Set up the project:

  1. Navigate to project: cd into the folder
  2. Initialize caccl using our wizard: run npm init caccl
  3. Choose app type: choose EJS + Express App
  4. Set up dev enironment: choose "y" and set up dev environment
  5. Add test course link: https://canvas.harvard.edu/courses... (copy your course link)
  6. Add access token (create an access token: try googling it)

Demo starter app:

  1. Start Canvas simulator: open new terminal tab, run npm run dev:canvas
  2. Start server: open new terminal tab, run npm run dev:server
  3. Simulate launch: go to the tab where you ran npm run dev:canvas and copy/paste link from canvas simulator into browser to demo the simulated launch process
  4. Point out resources and note that we will start by editing views/index.ejs and routes.js

Test editing views/index

  1. Delete instructions in views/index
  ...
  <body>
  
    <!-- Bootstrap alert -->
    <div class="alert alert-dark text-center m-5">
      <!-- Title -->
      <h3>It's great to meet you, <%= name %>!</h3>
  
      <!-- Instructions -->            /* Remove */
      <ul class="list-group">          /* Remove */
        <li class="list-group-item">   /* Remove */
          Start by editing <code>rou.../* Remove */
        </li>                          /* Remove */
        <li class="list-group-item">   /* Remove */
          Read the <a href="https://.../* Remove */
        </li>                          /* Remove */
        <li class="list-group-item">   /* Remove */
          Check out the <a href="htt.../* Remove */
        </li>                          /* Remove */
      </ul>                            /* Remove */
    </div>
  
  </body>
  1. Change title to "Hi there, <%= name %>!"
  ...
  <body>
  
    <!-- Bootstrap alert -->
    <div class="alert alert-dark text-center m-5">
      <!-- Title -->
      <h3>Hi There, <%= name %>!</h3>               /* Change */
    </div>
  
  </body>
  1. Refresh browser to show the update. Note the title change and the lack of instructions

Add all routes

  1. Remove starter content: open routes.js and delete the contents of the main function

    const path = require('path');
    
    module.exports = (app) => {
      // TODO: replace placehold... /* Remove */
      app.get('/', async (req, r... /* Remove */
        if (!req.api) {             /* Remove */
          // Not logged in          /* Remove */
          return res.send('Pleas... /* Remove */
        }                           /* Remove */
                                    /* Remove */
        try {                       /* Remove */
          // Get user profile.      /* Remove */
          const profile = await ... /* Remove */
                                    /* Remove */
          // Render the index pa... /* Remove */
          return res.render(path... /* Remove */
            name: profile.name,     /* Remove */
          });                       /* Remove */
        } catch (err) {             /* Remove */
          return res.send(`An er... /* Remove */
        }                           /* Remove */
      });                           /* Remove */
    };
  2. Refresh everyone on the 4 routes (visit slides)

  3. Add routes to routes.js:

    const path = require('path');
    
    module.exports = (app) => {
      app.get('/', async (req, res) => {            /* Add */
        // Show the Create Event page               /* Add */
      });                                           /* Add */
                                                    /* Add */
      app.post('/', async (req, res) => {           /* Add */
        // Capture input from Create Event page     /* Add */
      });                                           /* Add */
                                                    /* Add */
      app.get('/events/:id', async (req, res) => {  /* Add */
        // Show Scan to Attend page                 /* Add */
      });                                           /* Add */
                                                    /* Add */
      app.post('/events/:id', async (req, res) => { /* Add */
        // Capture input from Scan to Attend page   /* Add */
      });                                           /* Add */
    };

Show the Create Event page

  1. Render the Create Event page: edit routes.js
  ...
  app.get('/', async (req, res) => {
    // Show the Create Event page
    res.render(__dirname + '/views/index');  /* Add */
  });
  ...
  1. Add content to the Create Event: edit views/index

a. Change title to "Create Event"

```html
<body>

  <!-- Bootstrap alert -->
  <div class="alert alert-dark text-center m-5">
    <!-- Title -->
    <h3>Create Event</h3>                        /* Change */
  </div>

</body>
```

b. Create the create event form

```html
<body>

  <!-- Bootstrap alert -->
  <div class="alert alert-dark text-center m-5">
    <!-- Title -->
    <h3>Create Event</h3>

    <form method="POST">                          /* Add */
    </form>                                       /* Add */
  </div>

</body>
```

c. Add an input field in the form for the event title

```html
<body>

  <!-- Bootstrap alert -->
  <div class="alert alert-dark text-center m-5">
    <!-- Title -->
    <h3>Create Event</h3>
    
    <form method="POST">
      <input type="text" name="eventTitle" placeholder="Event Title" autofocus>  /* Add */
    </form>
  </div>

</body>
```
  1. Restart the server (it's been updated): kill the server process using ctrl + c, start it again using npm run dev:server
  2. Simulate launch: go to the tab where you ran npm run dev:canvas and copy/paste link from canvas simulator into browser to demo the simulated launch process
  3. Look at the Create Event page and see that it matches our design

Capture input from Create Event page and create 1-point assignment

  1. Back in routes.js, create a new 1-point assignment
  app.post('/', async (req, res) => {
    // Capture input from Create Event page
    const assignment = req.api.course.assignment.create({ /* Add */
      name: ____,                                         /* Add */
      pointsPossible: 1,                                  /* Add */
      published: true,                                    /* Add */
      courseId: _____,                                    /* Add */
    });                                                   /* Add */
  });
  1. Get title from form: let's get the title from the body of the form submission
  app.post('/', async (req, res) => {
    // Capture input from Create Event page
    const assignment = req.api.course.assignment.create({
      name: req.body.eventTitle,                          /* Change */
      pointsPossible: 1,
      published: true,
      courseId: _____,
    });
  });
  1. Get course id from launch info: when we launch the app, in that launch info, we get course information. CACCL adds that to req.session.launchInfo
  app.post('/', async (req, res) => {
    // Capture input from Create Event page
    const assignment = req.api.course.assignment.create({
      name: req.body.eventTitle,
      pointsPossible: 1,
      published: true,
      courseId: req.session.launchInfo.courseId,          /* Change */
    });
  });
  1. Wait for task to finish: this an asynchronous request, so let's wait for it to finish (add await)
  app.post('/', async (req, res) => {
    // Capture input from Create Event page
    const assignment = await req.api.course.assignment.create({   /* Change */
      name: req.body.eventTitle,
      pointsPossible: 1,
      published: true,
      courseId: req.session.launchInfo.courseId,
    });
  });
  1. Redirect user to the Scan to Attend Event page
  app.post('/', async (req, res) => {
    // Capture input from Create Event page
    const assignment = await req.api.course.assignment.create({
      name: req.body.eventTitle,
      pointsPossible: 1,
      published: true,
      courseId: req.session.launchInfo.courseId,
    });
  
    // Redirect user to event page                /* Add */
    res.redirect('/events/' + assignment.id);     /* Add */
  });

Show the Scan to Attend Event page

  1. Render the event page: edit routes.js
  app.get('/events/:id', async (req, res) => {
    // Show Scan to Attend page
    res.render(__dirname + '/views/event');   /* Add */
  });
  1. Duplicate views/index.ejs => views/event.ejs

  2. Add jQuery to <head> of the page

  <head>
    <title>CACCL App</title>
  
    <!-- Import Bootstrap -->
    <link
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
      crossorigin="anonymous"
    >
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script> /* Add */
  </head>
  ...
  1. Change title to "Scan to Attend":
  ...
  <body>

    <!-- Bootstrap alert -->
    <div class="alert alert-dark text-center m-5">
      <!-- Title -->
      <h3>Scan to Attend</h3>                         /* Change */
    </div>
  
    <form method="POST">
      <input type="text" name="eventTitle" placeholder="Event Title" autofocus>
    </form>
  
  </body>
  1. Update input field: change the name to "studentId", get rid of placeholder
  ...
  <body>

    <!-- Bootstrap alert -->
    <div class="alert alert-dark text-center m-5">
      <!-- Title -->
      <h3>Scan to Attend</h3>
    </div>
  
    <form method="POST">
      <input type="text" name="studentId" autofocus>  /* Change */
    </form>
  
  </body>
  1. Restart the server (it's been updated): kill the server process using ctrl + c, start it again using npm run dev:server
  2. Simulate launch: go to the tab where you ran npm run dev:canvas and copy/paste link from canvas simulator into browser to demo the simulated launch process
  3. Test the app so far:

a. Enter "Lab 1" as the title and press enter

b. See that the "Scan to Attend" page appears. Note that we can't enter someone's ID into the field because we haven't set up the app to capture the input from the Scan to Attend page. That's next!

c. Visit Canvas assignments to see that the assignment "Lab 1" was created

  1. Add a friendly welcome message in views/event.ejs
  ...
  <body>

    <!-- Bootstrap alert -->
    <div class="alert alert-dark text-center m-5">
      <!-- Title -->
      <h3>Scan to Attend</h3>
      
      <form method="POST">
        <input type="text" name="studentId" autofocus>
      </form>
    </div>
    
    <h3 id="welcome" class="alert alert-success text-center m-5"> /* Add */
      Welcome!                                                    /* Add */
    </h3>                                                         /* Add */
  
  </body>
  1. Hide the welcome message after 1s
  ...
  <body>

    <!-- Bootstrap alert -->
    <div class="alert alert-dark text-center m-5">
      <!-- Title -->
      <h3>Scan to Attend</h3>
      
      <form method="POST">
        <input type="text" name="studentId" autofocus>
      </form>
    </div>
    
    <h3 id="welcome" class="alert alert-success text-center m-5">
      Welcome!
    </h3>
  
    <script type="text/javascript">                               /* Add */
      $('#welcome').delay(1000).hide(200);                        /* Add */
    </script>                                                     /* Add */
  
  </body>
  1. Refresh the page and notice the "Welcome!" splash that appears

Capture the input from the Scan to Attend page and update grade

  1. Update the student's grade in routes.js
  app.post('/events/:id', async (req, res) => {
    // Capture input from Scan to Attend page
    req.api.course.assignment.updateGrade({     /* Add */
      courseId: ____,                           /* Add */
      assignmentId: ____,                       /* Add */
      studentId: ____,                          /* Add */
      points: 1,                                /* Add */
    });                                         /* Add */
  });
  1. Add course id: get this from the launch info that caccl adds automatically
  app.post('/events/:id', async (req, res) => {
    // Capture input from Scan to Attend page
    req.api.course.assignment.updateGrade({
      courseId: req.session.launchInfo.courseId,  /* Change */
      assignmentId: ____,
      studentId: ____,
      points: 1,
    });
  }); 
  1. Add assignment id: if you remember, events are just 1-point assignments. Thus, the event id is the assignment id. We get the event id from the url. We do this using req.params.id
  app.post('/events/:id', async (req, res) => {
    // Capture input from Scan to Attend page
    req.api.course.assignment.updateGrade({
      courseId: req.session.launchInfo.courseId,
      assignmentId: req.params.id,                /* Change */
      studentId: ____,
      points: 1,
    });
  }); 
  1. Add student id: this was typed into the form. We called that studentId (show the Scan to Attend page and point to the "name" field). We get the studentId from the form body using req.body.studentId
  app.post('/events/:id', async (req, res) => {
    // Capture input from Scan to Attend page
    req.api.course.assignment.updateGrade({
      courseId: req.session.launchInfo.courseId,
      assignmentId: req.params.id,
      studentId: req.body.studentId,              /* Change */
      points: 1,
    });
  }); 
  1. Redirect the user back to the swipe to attend page so we can take another attendance
  app.post('/events/:id', async (req, res) => {
    // Capture input from Scan to Attend page
    req.api.course.assignment.updateGrade({
      courseId: req.session.launchInfo.courseId,
      assignmentId: req.params.id,
      studentId: req.body.studentId,
      points: 1,
    });
    
    res.redirect('/events/' + req.params.id);     /* Add */
  }); 

End-to-end Demo

  1. Restart the server (it's been updated): kill the server process using ctrl + c, start it again using npm run dev:server
  2. Simulate launch: go to the tab where you ran npm run dev:canvas and copy/paste link from canvas simulator into browser to demo the simulated launch process
  3. Test the app:

a. Enter "Lab 2" as the title and press enter

b. Scan many students' barcode cards

c. Visit Canvas gradebook, see that students have their grades

Deploy

  1. Open our Heroku app, open "Deployment" tab and link to GitHub, turn on automatic deploys
  2. Open Settings, scroll to Config Vars, add the names to the variables:
  CONSUMER_KEY = consumer_key
  CONSUMER_SECRET = consumer_secret
  CLIENT_ID = (get this from HUIT)
  CLIENT_SECRET = (get this from HUIT)
  CANVAS_HOST = canvas.harvard.edu
  1. Fill in the config var values: unplug the computer, fill in the values, hide the config vars, plug the computer back in
  2. Push code to github
  git add -A
  git commit -m "Initial Commit"
  git push
  1. Try it out
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment