<!DOCTYPE html>
<html ng-app="sampleCaliperApp">

<head lang="en">
  <meta charset="utf-8">
  <title>IMS Caliper Analytics&trade; Sample Application</title>

  <!-- required libraries and CSS -->
  <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.13/angular.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.2/js/bootstrap.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.min.js"></script>

  <!-- caliper-js -->
  <script src="http://www.imsglobal.org/caliper/caliperSensor-1.0.0.js"></script>

  <!-- Plunkr JS and CSS -->
  <link rel="stylesheet" href="style.css">
  <script src="app.js"></script>
  <script src="sampleAppContextService.js"></script>
  <script src="sampleAppSensorService.js"></script>
</head>

<body ng-controller="sampleAppCtrl">

  <!-- Set up the Course Tabs -->
  <div class="container-fluid">
    <ul class="nav nav-tabs">
      <li role="presentation" ng-class="syllabusActive ? 'active' : 'inactive'"><a ng-click="setSyllabusActive()">Course Home</a>
      </li>
      <li role="presentation" ng-class="readingActive ? 'active' : 'inactive'"><a ng-click="setReadingActive()">Reading</a>
      </li>
      <li role="presentation" ng-class="quizActive ? 'active' : 'inactive'"><a ng-click="setQuizActive()">Quiz</a>
      </li>
    </ul>
  </div>

  <div class="container-fluid">
    <div ng-show="syllabusActive">
      <h3>Course Syllabus</h3>
      <div class="btn-group-vertical" role="group" aria-label="...">
        <a ng-click="setReadingActive()">Complete Reading</a>
        <br/>
        <a ng-click="setQuizActive()">Take a Quiz</a>
      </div>
    </div>
    <div ng-show="readingActive">
      <h3>Reading</h3>
      <h5>States of Matter</h5>
      <p>
        In physics, a state of matter is one of the distinct forms that matter takes on. Four states of matter are observable in everyday life: solid, liquid, gas, and plasma. Many other states are known such as Bose–Einstein condensates and neutron-degenerate matter but these only occur in extreme situations such as ultra cold or ultra dense matter. Other states, such as quark–gluon plasmas, are believed to be possible but remain theoretical for now. For a complete list of all exotic states of matter, see the list of states of matter.
      </p>
      <p>
        Historically, the distinction is made based on qualitative differences in properties. Matter in the solid state maintains a fixed volume and shape, with component particles (atoms, molecules or ions) close together and fixed into place. Matter in the liquid state maintains a fixed volume, but has a variable shape that adapts to fit its container. Its particles are still close together but move freely. Matter in the gaseous state has both variable volume and shape, adapting both to fit its container. Its particles are neither close together nor fixed in place. Matter in the plasma state has variable volume and shape, but as well as neutral atoms, it contains a significant number of ions and electrons, both of which can move around freely. Plasma is the most common form of visible matter in the universe.[1]
      </p>
      <br>
      <br>
      <form name="tagsForm" class="form-inline">
        <div class="form-group">
          <label for="tag">Enter Tag</label>
          <input type="text" class="form-control" ng-model="currentTag" id="tag" placeholder="-- enter tag --">
        </div>
        <button ng-click="addTag()" class="btn btn-default">Add</button>
      </form>
      <br>
      <br>
      <span>My Tags = {{readingTags}}</span>
    </div>
    <div ng-show="quizActive">
      <h3>Quiz</h3>
      <h5>States of Matter</h5>
      Which one of these is not a valid state of matter?
      <form name="myForm" ng-show="quizActive">
        <input type="checkbox" ng-model="plasma">Liquid
        <br/>
        <input type="checkbox" ng-model="solid">Solid
        <br/>
        <input type="checkbox" ng-model="correctAnswer">Plasmoid
        <br/>
        <input type="checkbox" ng-model="gas">Gas
        <br/>
        <br/>
      </form>
      <button ng-click="submitQuiz()">
        Submit
      </button>
      <button ng-click="retakeQuiz()">
        Reset
      </button>
      <br>
      <br>
      <span ng-show="quizSubmitted">Feedback: response is {{correctAnswer}}</span>
    </div>
  </div>

  <br><br>
  <div class="container-fluid">
    <div class="panel panel-success">
      <div class="panel-heading">
        <h3 class="panel-title">Last Caliper Event: {{ currentEventType }}</h3>
      </div>
      <div class="panel-body">
        <pre>{{ currentEvent | json }}</pre>
      </div>
    </div>
  </div>

</body>

</html>
/* Styles go here */

# IMS Global Caliper Analytics &#0153; Sample Application

Sample application that utilizes the IMS Global Javascript sensor (caliper-js).

This application leverages the IMS Global Caliper information model and Caliper Javascript sensor (caliper-js) in order to generate a sequence of Caliper events.

The application is modeled as a simple course delivery app that provides a syllabus, reading to annotate and quiz.

### Prerequisites:
* Familiarity with Javascript
* Familiarity with AngularJS (or at least an understanding of MVC)

### To run / use:
* You need to run an HTTP server... for e.g. on a Mac or Linux box you could execute the following command
```
> python -m SimpleHTTPServer 9999
```
* Then in a web browser, navigate to http://localhost:9999/index.html

### Instructions for Caliper Bootcamp or for Developers looking to test out caliper-js capabilities:
1. **Fork**: create a copy of the sample app by clicking the *Fork* button.
2. **Update**: if required, update index.html, line 16, with the URL of the caliper-js library (URL to be provided by bootcamp facilitators).
3. **RequestBin**: visit http://requestb.in and create a new requestbin. This will serve as a target endpoint for sending events for review/inspection.
4. **Path**: update the sensor.initialize() path attribute in sampleAppSensorService.js, line 39 with your RequestBin id.
5. **Preview**: click the plunk eye icon (vertical menu) to view the sample app.
6. **Run**: click the Plunk *Run* button.
7. **Refresh**: refresh your RequestBin page.  It should display events emitted by the caliper-js sensor as you interact with the sample app.

### Bootcamp Exercises:

#### Caliper Bootcamp Exercise #1 - add missing Navigation event properties
* Insert missing event properties for a user navigating to a Reading.
* **app.js**: you will need to fill in the function ```$scope.navigateToReadingPage()```

#### Caliper Bootcamp Exercise #2 - add a Tag Annotation event
* Build a tagged event for a user tagging a reading.
* **app.js**: you will need to fill in the function ```$scope.addTagsToPage()```

#### Caliper Bootcamp Exercise #3 - add a quiz submission event
* Build a quiz submitted event indicating that a user submitted a quiz attempt
* **app.js**: you will need to fill in the function ```$scope.submitQuiz()```

#### Caliper Bootcamp Exercise #4 - add a quiz outcome event
* Build a quiz outcome event that simulates an auto-graded quiz event
* **app.js**: you will need to create a new function ```$scope.gradeQuiz()```

&copy; 2015 IMS Global Learning Consortium, Inc. All  Rights Reserved.

&#0153; [Trademark  Information](a href="http://www.imsglobal.org/copyright.html")
/*
 * This file is part of IMS Caliper Analytics™ and is licensed to
 * IMS Global Learning Consortium, Inc. (http://www.imsglobal.org)
 * under one or more contributor license agreements.  See the NOTICE
 * file distributed with this work for additional information.
 *
 * IMS Caliper is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * IMS Caliper is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this program. If not, see http://www.gnu.org/licenses/.
 */

/* 
 * Sample App Context Service
 *
 * This service wraps the caliper-js sensor and provides a set of sample Caliper 
 * entities
 *
 * @author: Prashant Nayak, Intellify Learning; Anthony Whyte, University of Michigan
 */
angular.module('sampleCaliperApp')
  .service('sampleAppContextService', function() {

    /**
     * Decrement date by n days in order to create faux historical dates for entities.
     * @param date
     * @param decrement
     */
    var decrementDate = function(date, decrement) {
      date.setDate(date.getDate() - decrement);
      return date.toISOString();
    };

    /**
     * Increment date by n days in order to create faux future dates for entities
     * @param date
     * @param increment
     */
    var incrementDate = function(date, increment) {
      date.setDate(date.getDate() + increment);
      return date.toISOString();
    };

    // Get the current user as a Caliper Actor
    var getUser = function() {
      var actor = new Caliper.Entities.Person("https://example.edu/user/554433");
      actor.setDateCreated(decrementDate(new Date(), 45));
      return actor;
    };

    // Get the Syllabus
    var getSyllabus = function() {
      var syllabus = new Caliper.Entities.DigitalResource("https://example.edu/deptOfPhysics/2014/physics101/syllabus");
      syllabus.setName("Syllabus");
      syllabus.setVersion("1.0");
      syllabus.setDateCreated(decrementDate(new Date(), 14));
      syllabus.setDateModified(decrementDate(new Date(), 7));
      return syllabus;
    };

    // Get the current Reading
    var ePub = {};
    var getReading = function() {
      ePub = new Caliper.Entities.EPubVolume("https://github.com/readium/readium-js-viewer/book/34843#epubcfi(/4/3)");
      ePub.setName("States of Matter - A Condensed History");
      ePub.setVersion("1.0");
      ePub.setDateCreated(decrementDate(new Date(), 14));
      ePub.setDateModified(decrementDate(new Date(), 7));
      return ePub;
    };

    var getReadingFrame = function() {
      var ePubFrame = new Caliper.Entities.Frame("https://github.com/readium/readium-js-viewer/book/frame/34843#epubcfi(/4/3/1)");
      ePubFrame.setName("Introduction to the states of matter");
      ePubFrame.setIndex(1);
      ePubFrame.setIsPartOf(ePub);
      ePubFrame.setVersion(ePub.version);
      ePubFrame.setDateCreated(ePub.dateCreated);
      ePubFrame.setDateModified(ePub.dateModified);
      return ePubFrame;
    };

    // Get the current edApp
    var getEdApp = function() {
      var edApp = new Caliper.Entities.SoftwareApplication("https://imsglobal.org/sampleCaliperApp");
      edApp.setName("Sample Caliper App");
      edApp.setDateCreated(decrementDate(new Date(), 30));
      return edApp;
    };

    // Get the current Course
    var org = {};
    var getCourse = function() {
      org = new Caliper.Entities.CourseSection("https://example.edu/deptOfPhysics/2014/physics101");
      org.setCourseNumber("Phy-101");
      org.setName("Introductory Physics");
      org.setDescription("This is a Physics course for beginners.");
      org.setAcademicSession("Fall-2015");
      org.setDateCreated(decrementDate(new Date(), 30));
      org.setDateModified(decrementDate(new Date(), 28));
      return org;
    };

    // Get the membership
    var getMembership = function() {
      var member = getUser();
      var course = getCourse();

      var membership = new Caliper.Entities.Membership("https://example.edu/deptOfPhysics/2014/physics101/roster/554433");
      membership.setDescription("Roster entry");
      membership.setMember(member['@id']);
      membership.setOrganization(course['@id']);
      membership.setRoles([Caliper.Entities.Role.LEARNER]);
      membership.setStatus(Caliper.Entities.Status.ACTIVE);
      membership.setDateCreated(decrementDate(new Date(), 21));
      return membership;
    };

    // Get Home Page for current Course
    var getCourseHomePage = function() {
      var courseHomePage = new Caliper.Entities.WebPage("Physics101-Course-Homepage");
      courseHomePage.setName("Physics101-Course-Homepage");
      courseHomePage.setIsPartOf(org);
      courseHomePage.setDateCreated(decrementDate(new Date(), 28));
      courseHomePage.setDateModified(decrementDate(new Date(), 25));
      return courseHomePage;
    };

    // Get Quiz Page for current Course
    var getQuizPage = function() {
      var quizPage = new Caliper.Entities.WebPage("Physics101-Course-QuizPage");
      quizPage.setName("Physics101-Course-QuizPage");
      quizPage.setIsPartOf(org);
      quizPage.setDateCreated(decrementDate(new Date(), 28));
      quizPage.setDateModified(decrementDate(new Date(), 25));
      return quizPage;
    };

    // Get Quiz (Assessment) for current Course
    var getQuiz = function() {
      var quiz = new Caliper.Entities.Assessment("https://example.edu/deptOfPhysics/2014/physics101/assessment1");
      quiz.setName("States of Matter - Assessment");
      quiz.setIsPartOf("https://some-university.edu/deptOfPhysics/2014/physics101");
      quiz.setDateCreated(decrementDate(new Date(), 28));
      quiz.setDateModified(decrementDate(new Date(), 27));
      quiz.setDatePublished(decrementDate(new Date(), 14));
      quiz.setDateToActivate(decrementDate(new Date(), 13));
      quiz.setDateToShow(decrementDate(new Date(), 12));
      quiz.setDateToStartOn(incrementDate(new Date(), 7));
      quiz.setDateToSubmit(incrementDate(new Date(), 14));
      quiz.setMaxAttempts(2);
      quiz.setMaxSubmits(2);
      quiz.setMaxScore(3.0);

      // The Quiz has one AssessmentItem
      var assessmentItem1 = new Caliper.Entities.AssessmentItem("https://example.edu/deptOfPhysics/2014/physics101/assessment1/item1");
      assessmentItem1.setName("Assessment Item 1");
      assessmentItem1.setIsPartOf("https://example.edu/deptOfPhysics/2014/physics101/assessment1");
      assessmentItem1.setMaxAttempts(2);
      assessmentItem1.setMaxSubmits(2);
      assessmentItem1.setMaxScore(1.0);
      assessmentItem1.setDateCreated(quiz.dateCreated);
      assessmentItem1.setDateModified(quiz.dateModified);

      return quiz;
    };

    // Export the functions that will be used by other controllers and services
    var exports = {
      getUser: getUser,
      getSyllabus: getSyllabus,
      getReading: getReading,
      getReadingFrame: getReadingFrame,
      getEdApp: getEdApp,
      getCourse: getCourse,
      getMembership: getMembership,
      getCourseHomePage: getCourseHomePage,
      getQuizPage: getQuizPage,
      getQuiz: getQuiz
    };

    return exports;
  });
/*
 * This file is part of IMS Caliper Analytics™ and is licensed to
 * IMS Global Learning Consortium, Inc. (http://www.imsglobal.org)
 * under one or more contributor license agreements.  See the NOTICE
 * file distributed with this work for additional information.
 *
 * IMS Caliper is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * IMS Caliper is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this program. If not, see http://www.gnu.org/licenses/.
 */

/* 
 * Sample App Sensor Service
 *
 * This service wraps the caliper-js sensor, enabling any application specific 
 * logic to be performed before sending events using the base Javascript sensor
 *
 * @author: Prashant Nayak, Intellify Learning; Anthony Whyte, University of Michigan
 */
angular.module('sampleCaliperApp')
  .factory('sampleAppSensorService', ['sampleAppContextService', function(sampleAppContextService) {

    // Initialize Caliper sensor with options
    var sensor = Caliper.Sensor;

    // Note that you will have to create a new request bin
    // by navigating to http://requestb.in/
    // and replace the "path" setting below with the path 
    // to your request bin
    sensor.initialize('http://example.com/sensor/1',{
      host: 'requestb-in-1h04eq0e08pc.runscope.net',
      path: '/1muq2hd1', // REPLACE WITH YOUR REQUEST BIN PATH
      withCredentials: false
    });

    // Wrapper around Caliper Sensor's send()
    var send = function(event) {

      // Perform any pre-processing, etc.

      // Send Events using Caliper Sensor
      sensor.send(event);
    };

    // Wrapper around Caliper Sensor's describe()
    var describe = function(entity) {

      // Perform any pre-processing, etc.

      // Describe Entities using Caliper Sensor
      sensor.describe(entity);
    };

    // Export the functions that will be used by controller
    var exports = {
      describe: describe,
      send: send,
      currentUser: sampleAppContextService.getUser(),
      syllabus: sampleAppContextService.getSyllabus(),
      reading: sampleAppContextService.getReading(),
      readingFrame: sampleAppContextService.getReadingFrame(),
      edApp: sampleAppContextService.getEdApp(),
      course: sampleAppContextService.getCourse(),
      membership: sampleAppContextService.getMembership(),
      courseHomePage: sampleAppContextService.getCourseHomePage(),
      quiz: sampleAppContextService.getQuiz(),
      quizPage: sampleAppContextService.getQuizPage()
    };

    return exports;
  }]);
/*
 * This file is part of IMS Caliper Analytics™ and is licensed to
 * IMS Global Learning Consortium, Inc. (http://www.imsglobal.org)
 * under one or more contributor license agreements.  See the NOTICE
 * file distributed with this work for additional information.
 *
 * IMS Caliper is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * IMS Caliper is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this program. If not, see http://www.gnu.org/licenses/.
 */

/*
 * AngularJS Controller for Sample Caliper Application
 *
 * This controller manages the logic for the Sample Application
 * and uses the sensor service to send Caliper events
 *
 * @author: Prashant Nayak, Intellify Learning; Anthony Whyte, University of Michigan
 */

var app = angular.module('sampleCaliperApp', []);

app.controller('sampleAppCtrl', ['$scope', 'sampleAppSensorService',
  function($scope, sampleAppSensorService, $window, $location) {

    // Model(Flags) for controlling UI
    $scope.syllabusActive = true;
    $scope.readingActive = false;
    $scope.quizActive = false;

    // Model for Annotation (Tags)
    $scope.currentTag = '';
    $scope.readingTags = [];

    // Model for Quiz
    $scope.correctAnswer = false;
    $scope.quizSubmitted = false;
    $scope.currentAttempt = null; // set/get current assessment attempt
    $scope.currentEvent = {};

    $scope.init = function() {
      console.log("INIT: Session started, Caliper = %o");
      $scope.startSession(); // Fire Session Event
    };

    // Controller functions for UI
    $scope.setSyllabusActive = function() {
      $scope.navigateToHomePage(); // Fire Navigation Event
      $scope.syllabusActive = true;
      $scope.readingActive = false;
      $scope.quizActive = false;
    };

    $scope.setReadingActive = function() {
      $scope.navigateToReadingPage(); // Fire Navigation Event
      $scope.readingActive = true;
      $scope.quizActive = false;
      $scope.syllabusActive = false;
    };

    $scope.setQuizActive = function() {
      $scope.navigateToQuizPage(); // Fire Navigation Event
      $scope.startQuiz(); // Fire Assessment Event
      $scope.quizActive = true;
      $scope.readingActive = false;
      $scope.syllabusActive = false;
    };

    $scope.addTag = function() {
      $scope.readingTags.push($scope.currentTag);
      $scope.addTagsToPage($scope.readingTags); // Fire Annotation Event
      $scope.currentTag = '';
    };

    $scope.submitQuiz = function() {
      $scope.quizSubmitted = true;
      // if (correctAnswer) {
      //   // $scope.submitQuizToSensor(3.0); // Fire Assessment Event
      // } else {
      //   // $scope.submitQuizToSensor(0.0); // Fire Assessment Event
      // }
      // Generate an outcome against the quiz submission attempt
      $scope.gradeQuiz();
    };

    $scope.retakeQuiz = function() {
      $scope.quizSubmitted = false;
    };

    //////////////////////////////////////////////////////
    // Controller functions for specific Caliper Events //
    //////////////////////////////////////////////////////

    $scope.startSession = function() {

      // Actor LOGGED IN to edApp generating a new Session

      // The Actor for the Caliper Event
      var actor = sampleAppSensorService.currentUser;

      // The Action for the Caliper Event (Hint: Use SessionActions)
      var action = Caliper.Actions.SessionActions.LOGGED_IN;

      // The Object being interacted with by the Actor (edApp)
      var obj = sampleAppSensorService.edApp;

      // The generated object (Session) within the Event Object
      var generated = new Caliper.Entities.Session("https://imsglobal.org/sampleCaliperApp//session-123456789");
      generated.setName("session-123456789");
      generated.setDescription(null);
      generated.setActor(actor);
      var sessionStart = new Date().toISOString();
      generated.setDateCreated(sessionStart);
      generated.setStartedAtTime(sessionStart);
      generated.setEndedAtTime(null);
      generated.setDuration(null);

      // The edApp that is part of the Learning Context
      var edApp = obj;

      // Target
      var target = sampleAppSensorService.courseHomePage;

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The actor's membership, roles and status
      var membership = sampleAppSensorService.membership;

      // Create the Session Event (Uncomment and set references to objects)
      var event = new Caliper.Events.SessionEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setGenerated(generated);
      event.setTarget(target);
      event.setEdApp(edApp);
      event.setGroup(group);
      event.setMembership(membership);
      event.setEventTime(new Date().toISOString());

      // console.log('created session event %O', event);

      $scope.currentEventType = 'Session Event (LOGGED IN)';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    // Navigation Event
    $scope.navigateToHomePage = function() {

      // The Actor for the Event
      var actor = sampleAppSensorService.currentUser;

      // The Action for the Event
      var action = Caliper.Actions.NavigationActions.NAVIGATED_TO;

      // The Object being interacted with by the Actor
      var obj = sampleAppSensorService.syllabus;

      // The target object within the Event Object
      var target = sampleAppSensorService.courseHomePage;

      // The edApp that is part of the Learning Context
      var edApp = sampleAppSensorService.edApp;

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The actor's membership, roles and status
      var membership = sampleAppSensorService.membership;

      // Default navigation page
      var navigatedFrom = sampleAppSensorService.courseHomePage;

      // Reset if target value exists
      if ($scope.currentEvent.target) {
        navigatedFrom = $scope.currentEvent.target;
      }

      // Create the Navigation Event
      var event = new Caliper.Events.NavigationEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setTarget(target);
      if (target != sampleAppSensorService.courseHomePage) {
        event.setNavigatedFrom(navigatedFrom);
      }
      event.setEdApp(edApp);
      event.setGroup(group);
      event.setMembership(membership);
      event.setEventTime(new Date().toISOString());

      // console.log('created navigation event %O', event);

      $scope.currentEventType = 'Navigation Event';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    // Exercise 1: add missing Navigation Event properties (Caliper.Actions.NavigationActions.NAVIGATED_TO)
    $scope.navigateToReadingPage = function() {

      // The Actor for the Event
      var actor = sampleAppSensorService.currentUser;

      // The Action for the Event
      var action = Caliper.Actions.NavigationActions.NAVIGATED_TO;

      // The Object (Reading) being interacted with by the Actor
      var obj = sampleAppSensorService.reading;

      // The target object (frame) within the Event Object
      var target = sampleAppSensorService.readingFrame;

      // The edApp that is part of the Learning Context
      var edApp = sampleAppSensorService.edApp;

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The actor's membership, roles and status
      var membership = sampleAppSensorService.membership;

      // Default navigation page
      var navigatedFrom = sampleAppSensorService.courseHomePage;

      // Reset if target value exists
      if ($scope.currentEvent.target) {
        navigatedFrom = $scope.currentEvent.target;
      }

      // Create the Navigation Event
      var event = new Caliper.Events.NavigationEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setTarget(target);
      event.setNavigatedFrom(navigatedFrom);
      event.setEdApp(edApp);
      event.setGroup(group);
      event.setMembership(membership);
      event.setEventTime(new Date().toISOString());

      // console.log('created navigation event %O', event);

      $scope.currentEventType = 'Navigation Event';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    // Exercise 2: add an Annotation Event (Caliper.Actions.AnnotationActions.TAGGED)
    $scope.addTagsToPage = function(tags) {

      // The Actor for the Caliper Event
      var actor = sampleAppSensorService.currentUser;

      // The Action for the Caliper Event
      var action = Caliper.Actions.AnnotationActions.TAGGED;

      // The Object being interacted with by the Actor
      var obj = sampleAppSensorService.readingFrame;

      // The generated object (Tag annotation)
      var generated = new Caliper.Entities.TagAnnotation("https://imsglobal.org/sampleCaliperApp/tags/7654");
      generated.setTags(tags);
      generated.setDateCreated(new Date().toISOString());

      // The edApp that is part of the Learning Context
      var edApp = sampleAppSensorService.edApp;

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The actor's membership, roles and status
      var membership = sampleAppSensorService.membership;

      // Create the Annotation Event
      var event = new Caliper.Events.AnnotationEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setGenerated(generated);
      event.setEdApp(edApp);
      event.setGroup(group);
      event.setMembership(membership);
      event.setEventTime(new Date().toISOString());

      console.log('created annotation event %O', event);

      $scope.currentEventType = 'Annotation Event';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    // Navigation Event
    $scope.navigateToQuizPage = function() {

      // The Actor for the Event
      var actor = sampleAppSensorService.currentUser;

      // The Action for the Event
      var action = Caliper.Actions.NavigationActions.NAVIGATED_TO;

      // The Object (Reading) being interacted with by the Actor
      var obj = sampleAppSensorService.quiz;

      // The target object within the Event Object
      var target = sampleAppSensorService.quizPage;

      // Default navigation page
      var navigatedFrom = sampleAppSensorService.courseHomePage;

      // Reset if target value exists
      if ($scope.currentEvent.target) {
        navigatedFrom = $scope.currentEvent.target;
      }

      // The edApp that is part of the Learning Context
      var edApp = sampleAppSensorService.edApp;

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The actor's membership, roles and status
      var membership = sampleAppSensorService.membership;

      // Create the Navigation Event
      var event = new Caliper.Events.NavigationEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setTarget(target);
      event.setNavigatedFrom(navigatedFrom);
      event.setEdApp(edApp);
      event.setGroup(group);
      event.setMembership(membership);
      event.setEventTime(new Date().toISOString());

      // console.log('created navigation event %O', event);

      $scope.currentEventType = 'Navigation Event';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    // Exercise 3: add a Quiz Start Event (Caliper.Actions.AssessmentActions.STARTED)
    $scope.startQuiz = function() {

      // The Actor for the Caliper Event
      var actor = sampleAppSensorService.currentUser;

      // The Action for the Caliper Event
      var action = Caliper.Actions.AssessmentActions.STARTED;

      // The Object being interacted with by the Actor
      var obj = sampleAppSensorService.quiz;

      // The generated object (Attempt) within the Event Object
      var generated = new Caliper.Entities.Attempt("https://some-university.edu/deptOfPhysics/2014/physics101/assessment1/attempt1");
      generated.setName(null);
      generated.setDescription(null);
      generated.setActor(actor['@id']);
      generated.setAssignable(obj['@id']);
      generated.setDateCreated(new Date().toISOString());
      generated.setDateModified(null);
      generated.setCount(1);
      generated.setStartedAtTime(new Date().toISOString());
      generated.setEndedAtTime(null);
      generated.setDuration(null);

      // Assign to scope object so that the submit quiz event can reference it
      $scope.currentAttempt = generated;

      // The edApp that is part of the Learning Context
      var edApp = sampleAppSensorService.edApp;

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The actor's membership, roles and status
      var membership = sampleAppSensorService.membership;

      // Create the Assessment Event
      var event = new Caliper.Events.AssessmentEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setGenerated(generated);
      event.setEdApp(edApp);
      event.setGroup(group);
      event.setMembership(membership);
      event.setEventTime(new Date().toISOString());

      // console.log('created assessment event %O', event);

      $scope.currentEventType = 'Assessment Event (STARTED)';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    // Exercise 4: add a Quiz Submit Event (Caliper.Actions.AssessmentActions.SUBMITTED)
    $scope.submitQuizToSensor = function() {

      // Actor SUBMITTED Quiz Attempt

      // The Actor for the Caliper Event 
      var actor = sampleAppSensorService.currentUser;

      // The Action for the Caliper Event (Hint: Use AssessmentActions)
      var action = Caliper.Actions.AssessmentActions.SUBMITTED;

      // The object (Attempt) being submitted
      var obj = $scope.currentAttempt;
      obj.setEndedAtTime(new Date().toISOString());
      $scope.currentAttempt = obj;

      // The edApp that is part of the Learning Context
      var edApp = sampleAppSensorService.edApp;

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The actor's membership, roles and status
      var membership = sampleAppSensorService.membership;

      // Create the Assessment Event (Uncomment and set references to objects)
      var event = new Caliper.Events.AssessmentEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setEdApp(edApp);
      event.setGroup(group);
      event.setMembership(membership);
      event.setEventTime(new Date().toISOString());

      // console.log('created assessment event %O', event);

      $scope.currentEventType = 'Assessment Event (SUBMITTED)';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    // Exercise 5: add an Outcome Event (Caliper.Actions.OutcomeActions.GRADED)
    $scope.gradeQuiz = function() {

      //console.log("Sending Outcome Event");

      // The Actor/Scorer for the Caliper Event.  No Event.membership is set for this actor.
      var actor = sampleAppSensorService.edApp;

      // The Action for the Caliper Event (Hint: Use AssessmentActions)
      var action = Caliper.Actions.OutcomeActions.GRADED;

      // The object (Attempt) being submitted
      var obj = $scope.currentAttempt;

      // The generated object (Result)
      var generated = new Caliper.Entities.Result(obj['@id'] + "/result/1235");
      generated.setActor(actor['@id']);
      generated.setAssignable(obj.assignable);
      generated.setDateCreated(new Date().toISOString());
      generated.setNormalScore(1.0); // TODO Render score dynamic
      generated.setPenaltyScore(0.0);
      generated.setExtraCreditScore(0.0);
      generated.setTotalScore(1.0);
      generated.setCurvedTotalScore(1.0);
      generated.setCurveFactor(0.0);
      generated.setComment("Caliper rocks!");
      generated.setScoredBy(actor);

      // The LIS Course Section for the Event (part of Learning Context)
      var group = sampleAppSensorService.course;

      // The Outcome Event
      var event = new Caliper.Events.OutcomeEvent();
      event.setActor(actor);
      event.setAction(action);
      event.setObject(obj);
      event.setGenerated(generated);
      event.setEdApp(actor);
      event.setGroup(group);
      event.setEventTime(new Date().toISOString());

      $scope.currentEventType = 'Outcome Event (GRADED)';
      $scope.currentEvent = event;

      // Send the event (check RequestBin for event JSON)
      sampleAppSensorService.send(event);
    };

    $scope.init();
  }
]);