by Pavel Simakov 2013-05-15
The Problem
This extension lets you embed Khan Academy Exercises in your Course Builder course. You have instant access to over 400 existing exercises and can add your own. All exercise files, HTML, CSS and JavaScript are served from your own Course Builder application and are fully customizable. You can track all student attempts while solving the exercises and use Course Builder authoring interface to embed the exercises into lessons. Using this extension anyone can make learning complex subjects fun!
As of release 1.4, Course Builder allows integration of third-party extensions and modules. Using these capabilities, I quickly re-packaged Khan Academy Exercises as a Course Builder module. The module is reusable and is installed into Course Builder in seconds. A live demo is in my Course Builder Experimental Playground. The source code is on GitHub. In this post I describe step by step how this integration was developed. You will be able to do the same, if not better!
Step 1: Getting Started
Course Builder platform is modular and extensible. The easiest way to add your features is NOT to modify the existing source code, but to create a new module or custom component. My module name is khanex
, it will go into a folder /modules/khanex/…
Any module folder needs an empty __init__.py file and main implementation file named after the module (i.e. khanex.py).
A module must be enabled in the main.py
file. Open this file, add an import and enable the module. Here are my edits:
... import modules.khanex.khanex ... modules.khanex.khanex.register_module().enable() ...
A module registration function is a place where you connect your extensions to the rest of the Course Builder. I am adding the following:
- new static handler to serve all static resources of Khan Academy Exercises; these resources are packaged as a zip file; the handler will simply serve the files from the zip file
- new custom tag to visually represent a specific Khan Academy Exercise in a lesson page
- new dynamic handler to record student attempts solving the exercise
... def register_module(): """Registers this module in the registry.""" # register custom tag tags.Registry.add_tag_binding('khanex', KhanExerciseTag) # register handlers zip_handler = ( '/khan-exercises', sites.make_zip_handler(ZIP_FILE)) render_handler = ( '/khan-exercises/khan-exercises/indirect/', KhanExerciseRenderer) # register module global custom_module custom_module = custom_modules.Module( 'Khan Academy Exercise', 'A set of pages for delivering Khan Academy Exercises via ' 'Course Builder.', [], [render_handler, zip_handler]) return custom_module
All modules can be inspected by the administrator in the Deployment section of Course Builder instance. Our new module is here as well, highlighted in blue:
Step 2: Extending Lesson Editor
Course Builder uses XHTML to represent student facing content. Not only all existing HTML tags can be used to author the lesson, but you can create new tags! I will create a new tagkhanex
. Now I can use it in the lesson body, like this:
<h1>My lesson</h1> <p>My Exercise: <khanex name="adding_and_subtracting_negative_numbers"></khanex> </p>
Writing HTML by hand is so 1998... Instead, we simply tell Course Builder visual editor about the new tag properties. The editor will then let course author use the tag in either the WYSIWYG mode or the raw HTML mode as desired.
My tag will have only one property: a name of the specific exercise from the bundled Khan Academy Exercise library to load and show to a student. The tag properties are configured by constructing a tag schema object and overriding a method get_schema()
. Here are my edits (minus code that inspects the zip file and lists all exercises it finds in there):
def get_schema(self, unused_handler): """Make schema with a list of all exercises by inspecting a zip file.""" ... reg = schema_fields.FieldRegistry('Khan Exercises') reg.add_property( schema_fields.SchemaField( 'name', 'Exercises', 'select', optional=True, select_data=items, description=('The relative URL name of the exercise.'))) return reg
Now the tag can be managed automatically by the visual editor (as well as entered manually as XHTML) and will appear in the rich text edit toolbar. Like this:
Step 3: Extending Lesson Renderer
Next step is to write some code to convert our new khanex
tag into something a student can see. There are two options here: generate HTML on the fly or embed script
or iframe
. I will a script that embeds an iframe.
The rendering code goes into a render()
method. Here are my edits:
def render(self, node): """Embed just a <script> tag that will in turn create an <iframe>.""" name = node.attrib.get('name') caption = name.replace('_', ' ') return cElementTree.XML( """ <div style='width: 450px;'> Khan Academy Exercise: %s <br/> <script> // customize the style of the exercise iframe var ity_ef_style = "width: 750px;"; </script> <script src="%s" type="text/javascript"></script> </div>""" % ( cgi.escape(caption), 'khan-exercises/embed.js?static:%s' % name))
The runtime behind Khan Academy Exercises is undocumented and quite complex. I will not get into that fully here. The script to embed an exercise was written by me a while ago and has nothing specific to Course Builder. If you care to know, this is what happens behind the scene: khan-exercises/embed.js
script embeds an iframe, an iframe handler returns a content of the specific exercise definition HTML page, HTML page loads lots of JavaScript representing the meat of the Khan Academy Exercises framework, loaded script uses AJAX to fetch a visual template page into which an exercise is embedded visually, and finally script combines an exercise definition with the visual template to create the final rendition.
The variable EXERCISE_HTML_PAGE_RAW
holds the visual template. The HTTP handler KhanExerciseRenderer
serves both: this visual template as well as the exercise definition HTML files (after looking it up in the zip file). Here is what student will see:
Step 4: Recording Student Attempts
When student interacts with an exercise course author may want to know all the attempts he makes: correct or incorrect. The Khan Academy Exercises package I use has the callback that sends student interaction data to a server of your choice. We now need to implement the server-side handler that receives this data.
Class KhanExerciseRenderer
post method will do just that. It receives JSON data representing student actions. First, after proper authentication, the data is captured into as an Event for later processing:
... # record submission models.EventEntity.record( 'module-khanex.exercise-submit', self.get_user(), data) ...
You can see all captured events using Google App Engine datastore viewer. Here is what a typical event looks like:
Step 5: Recording Student Progress
A progress indicator is an empty, partially- or fully-filled circle, shown to a student across the Course. It shows the completion state of various components. Course Builder progress tracker manages this functionality. In the same handler above, after we figure out current unit and lesson, we update the progress indicator and record that student attempted a specific exercise:
... # update progress unit_id, lesson_id = self._get_unit_lesson_from(data) self.get_course().get_progress_tracker().put_activity_accessed( student, unit_id, lesson_id) ...
Now, answering a Khan Academy Exercise exercise will update the state of the progress indicator automatically as expected:
There are many interesting things one can do with data submission: record progress only if student gave a right answer, update student grade book, find related exercises, etc. I implemented the simplest.
Step 6: Enabling Dynamic Configuration Options
It’s common for a module to require global configuration parameters to be set by a system administrator. Course Builder supports dynamic configuration and provides user interface for managing configuration parameters automatically.
For illustration, let's define a new configuration property that holds a list of exercise names, allowed for use. The bundled Khan Academy Exercise library contains over 400 exercises, but we can now restrict these and only allow handful to be used by the course author. It takes just a couple of lines of code to define a new dynamic configuration property. Here is how I did it:
... from models.config import ConfigProperty ... WHITELISTED_EXERCISES = ConfigProperty( '_khanex_whitelisted', str, ( 'A white-listed exercises that can be show to students. If this list ' 'is empty, all exercises are available.'), default_value='', multiline=True) ...
Now that the property if defined it can be modified to a desired value at runtime using visual editor. Here is the new property (in blue) in the list of settings:
Click the "Override" button to bring up the editor and modify the value. Here is what the editor for the property looks like:
Note that the user interface was constructed automatically given a property definition. I did not have to do any work manually. Course Builder takes care of building user interface, storing property values in the datastore and propagating value updates to all front end application instances. The only thing left to do is to check the value before listing or serving specific exercise.
Step 7: Monitoring Performance in Real-Time
Modern web applications with large number of concurrent users must have built-in real-time monitoring. Course Builder provides this functionality out of the box and we can use it in a our new module. I create new >performance counter to count a number of times students submits an attempt to a server. It takes just couple lines of code to declare the counter and then update its value in the post()
method. Here are my edits:
... from models.counters import PerfCounter ... # declare ATTEMPT_COUNT = PerfCounter( 'gcb-khanex-attempt-count', 'A number of attempts made by all users on all exercises.') ... # update ATTEMPT_COUNT.inc() ...
The performance counter values are automatically aggregate across all front end application instances and displayed to the system administrator on the Metrics tab in real-time!
Step 8: Custom Analytics Dashboard
One can also create a custom DurableJob, compute exercise attempts statistics and then display the results to the course author via a dashboard. I have not done it in my module, but you can see how it's done in the peer review module.Step 9: Putting it All Together
We now are all done. In just 200 lines of code we fully integrated Khan Academy Exercises into Course Builder. Here is a LIVE DEMO! All that's left is to deploy the code to real Google App Engine instance. It takes just 1 minute to deploy!
The Final Word
Course Builder is modular and extensible. It is easy to create your own extension. It is easy to deploy and run at huge scale. Just focus on your subject matter, your creativity and your students.