diff --git a/djangoapps/courseware/views.py b/djangoapps/courseware/views.py
index f7d1ba9293..cca9674c42 100644
--- a/djangoapps/courseware/views.py
+++ b/djangoapps/courseware/views.py
@@ -84,7 +84,7 @@ def render_accordion(request,course,chapter,section):
parameter. Returns (initialization_javascript, content)'''
if not course:
course = "6.002 Spring 2012"
-
+
toc=content_parser.toc_from_xml(content_parser.course_file(request.user), chapter, section)
active_chapter=1
for i in range(len(toc)):
@@ -96,8 +96,7 @@ def render_accordion(request,course,chapter,section):
['format_url_params',content_parser.format_url_params],
['csrf',csrf(request)['csrf_token']]] + \
template_imports.items())
- return {'init_js':render_to_string('accordion_init.js',context),
- 'content':render_to_string('accordion.html',context)}
+ return render_to_string('accordion.html',context)
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
def render_section(request, section):
@@ -124,8 +123,8 @@ def render_section(request, section):
if 'init_js' not in module:
module['init_js']=''
- context={'init':accordion['init_js']+module['init_js'],
- 'accordion':accordion['content'],
+ context={'init':module['init_js'],
+ 'accordion':accordion,
'content':module['content'],
'csrf':csrf(request)['csrf_token']}
@@ -179,8 +178,8 @@ def index(request, course="6.002 Spring 2012", chapter="Using the System", secti
if 'init_js' not in module:
module['init_js']=''
- context={'init':accordion['init_js']+module['init_js'],
- 'accordion':accordion['content'],
+ context={'init':module['init_js'],
+ 'accordion':accordion,
'content':module['content'],
'csrf':csrf(request)['csrf_token']}
diff --git a/requirements.txt b/requirements.txt
index 6e13b88463..8e701ce335 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -15,3 +15,4 @@ path.py
django_debug_toolbar
django-masquerade
pyfilesystem
+django-jasmine
diff --git a/settings.py b/settings.py
index f20f51ffb8..0828f75595 100644
--- a/settings.py
+++ b/settings.py
@@ -147,6 +147,7 @@ INSTALLED_APPS = (
'perfstats',
'util',
'masquerade',
+ 'django_jasmine',
# Uncomment the next line to enable the admin:
# 'django.contrib.admin',
# Uncomment the next line to enable admin documentation:
@@ -347,6 +348,7 @@ PROJECT_ROOT = os.path.dirname(__file__)
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.request',
+ 'django.core.context_processors.static',
'askbot.context.application_settings',
#'django.core.context_processors.i18n',
'askbot.user_messages.context_processors.user_messages',#must be before auth
@@ -683,3 +685,5 @@ if MAKO_MODULE_DIR == None:
djcelery.setup_loader()
+# Jasmine Settings
+JASMINE_TEST_DIRECTORY = PROJECT_DIR+'/templates/coffee'
diff --git a/static/js/application.js b/static/js/application.js
new file mode 100644
index 0000000000..6a2e883917
--- /dev/null
+++ b/static/js/application.js
@@ -0,0 +1,120 @@
+// Generated by CoffeeScript 1.3.2-pre
+(function() {
+
+ window.Calculator = (function() {
+
+ function Calculator() {}
+
+ Calculator.bind = function() {
+ var calculator;
+ calculator = new Calculator;
+ $('.calc').click(calculator.toggle);
+ $('form#calculator').submit(calculator.calculate).submit(function(e) {
+ return e.preventDefault();
+ });
+ return $('div.help-wrapper a').hover(calculator.helpToggle).click(function(e) {
+ return e.preventDefault();
+ });
+ };
+
+ Calculator.prototype.toggle = function() {
+ $('li.calc-main').toggleClass('open');
+ $('#calculator_wrapper #calculator_input').focus();
+ return $('.calc').toggleClass('closed');
+ };
+
+ Calculator.prototype.helpToggle = function() {
+ return $('.help').toggleClass('shown');
+ };
+
+ Calculator.prototype.calculate = function() {
+ return $.getJSON('/calculate', {
+ equation: $('#calculator_input').val()
+ }, function(data) {
+ return $('#calculator_output').val(data.result);
+ });
+ };
+
+ return Calculator;
+
+ })();
+
+ window.Courseware = (function() {
+
+ function Courseware() {}
+
+ Courseware.bind = function() {
+ return this.Navigation.bind();
+ };
+
+ Courseware.Navigation = (function() {
+
+ function Navigation() {}
+
+ Navigation.bind = function() {
+ var navigation;
+ if ($('#accordion').length) {
+ navigation = new Navigation;
+ $('#accordion').bind('accordionchange', navigation.log).accordion({
+ active: $('#accordion ul:has(li.active)').index('#accordion ul'),
+ header: 'h3',
+ autoHeight: false
+ });
+ return $('#open_close_accordion a').click(navigation.toggle);
+ }
+ };
+
+ Navigation.prototype.log = function(event, ui) {
+ return log_event('accordion', {
+ newheader: ui.newHeader.text(),
+ oldheader: ui.oldHeader.text()
+ });
+ };
+
+ Navigation.prototype.toggle = function() {
+ return $('.course-wrapper').toggleClass('closed');
+ };
+
+ return Navigation;
+
+ })();
+
+ return Courseware;
+
+ }).call(this);
+
+ window.FeedbackForm = (function() {
+
+ function FeedbackForm() {}
+
+ FeedbackForm.bind = function() {
+ return $('#feedback_button').click(function() {
+ var data;
+ data = {
+ subject: $('#feedback_subject').val(),
+ message: $('#feedback_message').val(),
+ url: window.location.href
+ };
+ return $.post('/send_feedback', data, function() {
+ return $('#feedback_div').html('Feedback submitted. Thank you');
+ }, 'json');
+ });
+ };
+
+ return FeedbackForm;
+
+ })();
+
+ $(function() {
+ $.ajaxSetup({
+ headers: {
+ 'X-CSRFToken': $.cookie('csrftoken')
+ }
+ });
+ Calculator.bind();
+ Courseware.bind();
+ FeedbackForm.bind();
+ return $("a[rel*=leanModal]").leanModal();
+ });
+
+}).call(this);
diff --git a/templates/accordion_init.js b/templates/accordion_init.js
deleted file mode 100644
index 7c4ca2603c..0000000000
--- a/templates/accordion_init.js
+++ /dev/null
@@ -1,19 +0,0 @@
-$("#accordion").accordion({
- active: ${ active_chapter },
- header: 'h3',
- autoHeight: false,
-});
-
-$("#open_close_accordion a").click(function(){
- if ($(".course-wrapper").hasClass("closed")){
- $(".course-wrapper").removeClass("closed");
- } else {
- $(".course-wrapper").addClass("closed");
- }
-});
-
-$('.ui-accordion').bind('accordionchange', function(event, ui) {
- var event_data = {'newheader':ui.newHeader.text(),
- 'oldheader':ui.oldHeader.text()};
- log_event('accordion', event_data);
-});
diff --git a/templates/coffee/README.md b/templates/coffee/README.md
new file mode 100644
index 0000000000..6c26529a5b
--- /dev/null
+++ b/templates/coffee/README.md
@@ -0,0 +1,59 @@
+CoffeeScript
+============
+
+This folder contains the CoffeeScript file that will be compiled to the static
+directory. By default, we're compile and merge all the files ending `.coffee`
+into `static/js/application.js`.
+
+Install the Compiler
+--------------------
+
+CoffeeScript compiler are written in JavaScript. You'll need to install Node and
+npm (Node Package Manager) to be able to install the CoffeeScript compiler.
+
+### Mac OS X
+
+Install Node via Homebrew, then use npm:
+
+ brew install node
+ curl http://npmjs.org/install.sh | sh
+ npm install -g git://github.com/jashkenas/coffee-script.git
+
+(Note that we're using the edge version of CoffeeScript for now, as there was
+some issue with directory watching in 1.3.1.)
+
+Try to run `coffee` and make sure you get a coffee prompt.
+
+### Debian/Ubuntu
+
+Conveniently, you can install Node via `apt-get`, then use npm:
+
+ sudo apt-get install nodejs npm &&
+ sudo npm install -g git://github.com/jashkenas/coffee-script.git
+
+Compiling
+---------
+
+Run this command in the `mitx` directory to easily make the compiler watch for
+changes in your file, and join the result into `application.js`:
+
+ coffee -j static/js/application.js -cw templates/coffee/src
+
+Please note that the compiler will not be able to detect the file that get added
+after you've ran the command, so you'll need to restart the compiler if there's
+a new CoffeeScript file.
+
+Testing
+=======
+
+We're also using Jasmine to unit-testing the JavaScript files. All the specs are
+written in CoffeeScript for the consistency. Because of the limitation of
+`django-jasmine` plugin, we'll need to also running another compiler to compile
+the test file.
+
+Using this command to compile the test files:
+
+ coffee -cw templates/coffee/spec/*.coffee
+
+Then start the server in debug mode, navigate to http://127.0.0.1:8000/_jasmine
+to see the test result.
diff --git a/templates/coffee/files.json b/templates/coffee/files.json
new file mode 100644
index 0000000000..bfae4dfe87
--- /dev/null
+++ b/templates/coffee/files.json
@@ -0,0 +1,10 @@
+{
+ "js_files": [
+ "/static/js/jquery-1.6.2.min.js",
+ "/static/js/jquery-ui-1.8.16.custom.min.js",
+ "/static/js/jquery.leanModal.js"
+ ],
+ "static_files": [
+ "js/application.js"
+ ]
+}
diff --git a/templates/coffee/fixtures/accordion.html b/templates/coffee/fixtures/accordion.html
new file mode 100644
index 0000000000..148c245c8f
--- /dev/null
+++ b/templates/coffee/fixtures/accordion.html
@@ -0,0 +1,6 @@
+
diff --git a/templates/coffee/fixtures/calculator.html b/templates/coffee/fixtures/calculator.html
new file mode 100644
index 0000000000..61c6f5e153
--- /dev/null
+++ b/templates/coffee/fixtures/calculator.html
@@ -0,0 +1,18 @@
+
diff --git a/templates/coffee/fixtures/feedback_form.html b/templates/coffee/fixtures/feedback_form.html
new file mode 100644
index 0000000000..672663fe10
--- /dev/null
+++ b/templates/coffee/fixtures/feedback_form.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/templates/coffee/spec/calculator_spec.coffee b/templates/coffee/spec/calculator_spec.coffee
new file mode 100644
index 0000000000..5c3fde5e2d
--- /dev/null
+++ b/templates/coffee/spec/calculator_spec.coffee
@@ -0,0 +1,68 @@
+describe 'Calculator', ->
+ beforeEach ->
+ loadFixtures 'calculator.html'
+ @calculator = new Calculator
+
+ describe 'bind', ->
+ beforeEach ->
+ Calculator.bind()
+
+ it 'bind the calculator button', ->
+ expect($('.calc')).toHandleWith 'click', @calculator.toggle
+
+ it 'bind the help button', ->
+ # These events are bind by $.hover()
+ expect($('div.help-wrapper a')).toHandleWith 'mouseenter', @calculator.helpToggle
+ expect($('div.help-wrapper a')).toHandleWith 'mouseleave', @calculator.helpToggle
+
+ it 'prevent default behavior on help button', ->
+ $('div.help-wrapper a').click (e) ->
+ expect(e.isDefaultPrevented()).toBeTruthy()
+ $('div.help-wrapper a').click()
+
+ it 'bind the calculator submit', ->
+ expect($('form#calculator')).toHandleWith 'submit', @calculator.calculate
+
+ it 'prevent default behavior on form submit', ->
+ $('form#calculator').submit (e) ->
+ expect(e.isDefaultPrevented()).toBeTruthy()
+ e.preventDefault()
+ $('form#calculator').submit()
+
+ describe 'toggle', ->
+ it 'toggle the calculator and focus the input', ->
+ spyOn $.fn, 'focus'
+ @calculator.toggle()
+
+ expect($('li.calc-main')).toHaveClass('open')
+ expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled()
+
+ it 'toggle the close button on the calculator button', ->
+ @calculator.toggle()
+ expect($('.calc')).toHaveClass('closed')
+
+ @calculator.toggle()
+ expect($('.calc')).not.toHaveClass('closed')
+
+ describe 'helpToggle', ->
+ it 'toggle the help overlay', ->
+ @calculator.helpToggle()
+ expect($('.help')).toHaveClass('shown')
+
+ @calculator.helpToggle()
+ expect($('.help')).not.toHaveClass('shown')
+
+ describe 'calculate', ->
+ beforeEach ->
+ $('#calculator_input').val '1+2'
+ spyOn($, 'getJSON').andCallFake (url, data, callback) ->
+ callback({ result: 3 })
+ @calculator.calculate()
+
+ it 'send data to /calculate', ->
+ expect($.getJSON).toHaveBeenCalledWith '/calculate',
+ equation: '1+2'
+ , jasmine.any(Function)
+
+ it 'update the calculator output', ->
+ expect($('#calculator_output').val()).toEqual('3')
diff --git a/templates/coffee/spec/calculator_spec.js b/templates/coffee/spec/calculator_spec.js
new file mode 100644
index 0000000000..6e0f8a0dab
--- /dev/null
+++ b/templates/coffee/spec/calculator_spec.js
@@ -0,0 +1,80 @@
+// Generated by CoffeeScript 1.3.2-pre
+(function() {
+
+ describe('Calculator', function() {
+ beforeEach(function() {
+ loadFixtures('calculator.html');
+ return this.calculator = new Calculator;
+ });
+ describe('bind', function() {
+ beforeEach(function() {
+ return Calculator.bind();
+ });
+ it('bind the calculator button', function() {
+ return expect($('.calc')).toHandleWith('click', this.calculator.toggle);
+ });
+ it('bind the help button', function() {
+ expect($('div.help-wrapper a')).toHandleWith('mouseenter', this.calculator.helpToggle);
+ return expect($('div.help-wrapper a')).toHandleWith('mouseleave', this.calculator.helpToggle);
+ });
+ it('prevent default behavior on help button', function() {
+ $('div.help-wrapper a').click(function(e) {
+ return expect(e.isDefaultPrevented()).toBeTruthy();
+ });
+ return $('div.help-wrapper a').click();
+ });
+ it('bind the calculator submit', function() {
+ return expect($('form#calculator')).toHandleWith('submit', this.calculator.calculate);
+ });
+ return it('prevent default behavior on form submit', function() {
+ $('form#calculator').submit(function(e) {
+ expect(e.isDefaultPrevented()).toBeTruthy();
+ return e.preventDefault();
+ });
+ return $('form#calculator').submit();
+ });
+ });
+ describe('toggle', function() {
+ it('toggle the calculator and focus the input', function() {
+ spyOn($.fn, 'focus');
+ this.calculator.toggle();
+ expect($('li.calc-main')).toHaveClass('open');
+ return expect($('#calculator_wrapper #calculator_input').focus).toHaveBeenCalled();
+ });
+ return it('toggle the close button on the calculator button', function() {
+ this.calculator.toggle();
+ expect($('.calc')).toHaveClass('closed');
+ this.calculator.toggle();
+ return expect($('.calc')).not.toHaveClass('closed');
+ });
+ });
+ describe('helpToggle', function() {
+ return it('toggle the help overlay', function() {
+ this.calculator.helpToggle();
+ expect($('.help')).toHaveClass('shown');
+ this.calculator.helpToggle();
+ return expect($('.help')).not.toHaveClass('shown');
+ });
+ });
+ return describe('calculate', function() {
+ beforeEach(function() {
+ $('#calculator_input').val('1+2');
+ spyOn($, 'getJSON').andCallFake(function(url, data, callback) {
+ return callback({
+ result: 3
+ });
+ });
+ return this.calculator.calculate();
+ });
+ it('send data to /calculate', function() {
+ return expect($.getJSON).toHaveBeenCalledWith('/calculate', {
+ equation: '1+2'
+ }, jasmine.any(Function));
+ });
+ return it('update the calculator output', function() {
+ return expect($('#calculator_output').val()).toEqual('3');
+ });
+ });
+ });
+
+}).call(this);
diff --git a/templates/coffee/spec/courseware_spec.coffee b/templates/coffee/spec/courseware_spec.coffee
new file mode 100644
index 0000000000..5a5cabac0e
--- /dev/null
+++ b/templates/coffee/spec/courseware_spec.coffee
@@ -0,0 +1,66 @@
+describe 'Courseware', ->
+ describe 'bind', ->
+ it 'bind the navigation', ->
+ spyOn Courseware.Navigation, 'bind'
+ Courseware.bind()
+ expect(Courseware.Navigation.bind).toHaveBeenCalled()
+
+ describe 'Navigation', ->
+ beforeEach ->
+ loadFixtures 'accordion.html'
+ @navigation = new Courseware.Navigation
+
+ describe 'bind', ->
+ describe 'when the #accordion exists', ->
+ it 'activate the accordion with correct active section', ->
+ spyOn $.fn, 'accordion'
+ $('#accordion').append('')
+ Courseware.Navigation.bind()
+ expect($('#accordion').accordion).toHaveBeenCalledWith
+ active: 1
+ header: 'h3'
+ autoHeight: false
+
+ it 'binds the accordionchange event', ->
+ Courseware.Navigation.bind()
+ expect($('#accordion')).toHandleWith 'accordionchange', @navigation.log
+
+ it 'bind the navigation toggle', ->
+ Courseware.Navigation.bind()
+ expect($('#open_close_accordion a')).toHandleWith 'click', @navigation.toggle
+
+ describe 'when the #accordion does not exists', ->
+ beforeEach ->
+ $('#accordion').remove()
+
+ it 'does not activate the accordion', ->
+ spyOn $.fn, 'accordion'
+ Courseware.Navigation.bind()
+ expect($('#accordion').accordion).wasNotCalled()
+
+ describe 'toggle', ->
+ it 'toggle closed class on the wrapper', ->
+ $('.course-wrapper').removeClass('closed')
+
+ @navigation.toggle()
+ expect($('.course-wrapper')).toHaveClass('closed')
+
+ @navigation.toggle()
+ expect($('.course-wrapper')).not.toHaveClass('closed')
+
+ describe 'log', ->
+ beforeEach ->
+ window.log_event = ->
+ spyOn window, 'log_event'
+
+ it 'submit event log', ->
+ @navigation.log {}, {
+ newHeader:
+ text: -> "new"
+ oldHeader:
+ text: -> "old"
+ }
+
+ expect(window.log_event).toHaveBeenCalledWith 'accordion',
+ newheader: 'new'
+ oldheader: 'old'
diff --git a/templates/coffee/spec/courseware_spec.js b/templates/coffee/spec/courseware_spec.js
new file mode 100644
index 0000000000..195ea7b73b
--- /dev/null
+++ b/templates/coffee/spec/courseware_spec.js
@@ -0,0 +1,85 @@
+// Generated by CoffeeScript 1.3.2-pre
+(function() {
+
+ describe('Courseware', function() {
+ describe('bind', function() {
+ return it('bind the navigation', function() {
+ spyOn(Courseware.Navigation, 'bind');
+ Courseware.bind();
+ return expect(Courseware.Navigation.bind).toHaveBeenCalled();
+ });
+ });
+ return describe('Navigation', function() {
+ beforeEach(function() {
+ loadFixtures('accordion.html');
+ return this.navigation = new Courseware.Navigation;
+ });
+ describe('bind', function() {
+ describe('when the #accordion exists', function() {
+ it('activate the accordion with correct active section', function() {
+ spyOn($.fn, 'accordion');
+ $('#accordion').append('');
+ Courseware.Navigation.bind();
+ return expect($('#accordion').accordion).toHaveBeenCalledWith({
+ active: 1,
+ header: 'h3',
+ autoHeight: false
+ });
+ });
+ it('binds the accordionchange event', function() {
+ Courseware.Navigation.bind();
+ return expect($('#accordion')).toHandleWith('accordionchange', this.navigation.log);
+ });
+ return it('bind the navigation toggle', function() {
+ Courseware.Navigation.bind();
+ return expect($('#open_close_accordion a')).toHandleWith('click', this.navigation.toggle);
+ });
+ });
+ return describe('when the #accordion does not exists', function() {
+ beforeEach(function() {
+ return $('#accordion').remove();
+ });
+ return it('does not activate the accordion', function() {
+ spyOn($.fn, 'accordion');
+ Courseware.Navigation.bind();
+ return expect($('#accordion').accordion).wasNotCalled();
+ });
+ });
+ });
+ describe('toggle', function() {
+ return it('toggle closed class on the wrapper', function() {
+ $('.course-wrapper').removeClass('closed');
+ this.navigation.toggle();
+ expect($('.course-wrapper')).toHaveClass('closed');
+ this.navigation.toggle();
+ return expect($('.course-wrapper')).not.toHaveClass('closed');
+ });
+ });
+ return describe('log', function() {
+ beforeEach(function() {
+ window.log_event = function() {};
+ return spyOn(window, 'log_event');
+ });
+ return it('submit event log', function() {
+ this.navigation.log({}, {
+ newHeader: {
+ text: function() {
+ return "new";
+ }
+ },
+ oldHeader: {
+ text: function() {
+ return "old";
+ }
+ }
+ });
+ return expect(window.log_event).toHaveBeenCalledWith('accordion', {
+ newheader: 'new',
+ oldheader: 'old'
+ });
+ });
+ });
+ });
+ });
+
+}).call(this);
diff --git a/templates/coffee/spec/feedback_form_spec.coffee b/templates/coffee/spec/feedback_form_spec.coffee
new file mode 100644
index 0000000000..191645b3d3
--- /dev/null
+++ b/templates/coffee/spec/feedback_form_spec.coffee
@@ -0,0 +1,28 @@
+describe 'FeedbackForm', ->
+ beforeEach ->
+ loadFixtures 'feedback_form.html'
+
+ describe 'bind', ->
+ beforeEach ->
+ FeedbackForm.bind()
+ spyOn($, 'post').andCallFake (url, data, callback, format) ->
+ callback()
+
+ it 'binds to the #feedback_button', ->
+ expect($('#feedback_button')).toHandle 'click'
+
+ it 'post data to /send_feedback on click', ->
+ $('#feedback_subject').val 'Awesome!'
+ $('#feedback_message').val 'This site is really good.'
+ $('#feedback_button').click()
+
+ expect($.post).toHaveBeenCalledWith '/send_feedback', {
+ subject: 'Awesome!'
+ message: 'This site is really good.'
+ url: window.location.href
+ }, jasmine.any(Function), 'json'
+
+ it 'replace the form with a thank you message', ->
+ $('#feedback_button').click()
+
+ expect($('#feedback_div').html()).toEqual 'Feedback submitted. Thank you'
diff --git a/templates/coffee/spec/feedback_form_spec.js b/templates/coffee/spec/feedback_form_spec.js
new file mode 100644
index 0000000000..2815cd73e5
--- /dev/null
+++ b/templates/coffee/spec/feedback_form_spec.js
@@ -0,0 +1,35 @@
+// Generated by CoffeeScript 1.3.2-pre
+(function() {
+
+ describe('FeedbackForm', function() {
+ beforeEach(function() {
+ return loadFixtures('feedback_form.html');
+ });
+ return describe('bind', function() {
+ beforeEach(function() {
+ FeedbackForm.bind();
+ return spyOn($, 'post').andCallFake(function(url, data, callback, format) {
+ return callback();
+ });
+ });
+ it('binds to the #feedback_button', function() {
+ return expect($('#feedback_button')).toHandle('click');
+ });
+ it('post data to /send_feedback on click', function() {
+ $('#feedback_subject').val('Awesome!');
+ $('#feedback_message').val('This site is really good.');
+ $('#feedback_button').click();
+ return expect($.post).toHaveBeenCalledWith('/send_feedback', {
+ subject: 'Awesome!',
+ message: 'This site is really good.',
+ url: window.location.href
+ }, jasmine.any(Function), 'json');
+ });
+ return it('replace the form with a thank you message', function() {
+ $('#feedback_button').click();
+ return expect($('#feedback_div').html()).toEqual('Feedback submitted. Thank you');
+ });
+ });
+ });
+
+}).call(this);
diff --git a/templates/coffee/spec/helper.coffee b/templates/coffee/spec/helper.coffee
new file mode 100644
index 0000000000..1f27e257c2
--- /dev/null
+++ b/templates/coffee/spec/helper.coffee
@@ -0,0 +1 @@
+jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/"
diff --git a/templates/coffee/spec/helper.js b/templates/coffee/spec/helper.js
new file mode 100644
index 0000000000..23b980d525
--- /dev/null
+++ b/templates/coffee/spec/helper.js
@@ -0,0 +1,6 @@
+// Generated by CoffeeScript 1.3.2-pre
+(function() {
+
+ jasmine.getFixtures().fixturesPath = "/_jasmine/fixtures/";
+
+}).call(this);
diff --git a/templates/coffee/src/calculator.coffee b/templates/coffee/src/calculator.coffee
new file mode 100644
index 0000000000..7d62f5a794
--- /dev/null
+++ b/templates/coffee/src/calculator.coffee
@@ -0,0 +1,20 @@
+class window.Calculator
+ @bind: ->
+ calculator = new Calculator
+ $('.calc').click calculator.toggle
+ $('form#calculator').submit(calculator.calculate).submit (e) ->
+ e.preventDefault()
+ $('div.help-wrapper a').hover(calculator.helpToggle).click (e) ->
+ e.preventDefault()
+
+ toggle: ->
+ $('li.calc-main').toggleClass 'open'
+ $('#calculator_wrapper #calculator_input').focus()
+ $('.calc').toggleClass 'closed'
+
+ helpToggle: ->
+ $('.help').toggleClass 'shown'
+
+ calculate: ->
+ $.getJSON '/calculate', { equation: $('#calculator_input').val() }, (data) ->
+ $('#calculator_output').val(data.result)
diff --git a/templates/coffee/src/courseware.coffee b/templates/coffee/src/courseware.coffee
new file mode 100644
index 0000000000..7772c1e533
--- /dev/null
+++ b/templates/coffee/src/courseware.coffee
@@ -0,0 +1,21 @@
+class window.Courseware
+ @bind: ->
+ @Navigation.bind()
+
+ class @Navigation
+ @bind: ->
+ if $('#accordion').length
+ navigation = new Navigation
+ $('#accordion').bind('accordionchange', navigation.log).accordion
+ active: $('#accordion ul:has(li.active)').index('#accordion ul')
+ header: 'h3'
+ autoHeight: false
+ $('#open_close_accordion a').click navigation.toggle
+
+ log: (event, ui) ->
+ log_event 'accordion',
+ newheader: ui.newHeader.text()
+ oldheader: ui.oldHeader.text()
+
+ toggle: ->
+ $('.course-wrapper').toggleClass('closed')
diff --git a/templates/coffee/src/feedback_form.coffee b/templates/coffee/src/feedback_form.coffee
new file mode 100644
index 0000000000..bbb5c09365
--- /dev/null
+++ b/templates/coffee/src/feedback_form.coffee
@@ -0,0 +1,10 @@
+class window.FeedbackForm
+ @bind: ->
+ $('#feedback_button').click ->
+ data =
+ subject: $('#feedback_subject').val()
+ message: $('#feedback_message').val()
+ url: window.location.href
+ $.post '/send_feedback', data, ->
+ $('#feedback_div').html 'Feedback submitted. Thank you'
+ ,'json'
diff --git a/templates/coffee/src/main.coffee b/templates/coffee/src/main.coffee
new file mode 100644
index 0000000000..8a9a892f94
--- /dev/null
+++ b/templates/coffee/src/main.coffee
@@ -0,0 +1,8 @@
+$ ->
+ $.ajaxSetup
+ headers : { 'X-CSRFToken': $.cookie 'csrftoken' }
+
+ Calculator.bind()
+ Courseware.bind()
+ FeedbackForm.bind()
+ $("a[rel*=leanModal]").leanModal()
diff --git a/templates/main.html b/templates/main.html
index 7078ed64d3..59a1ed7bd0 100644
--- a/templates/main.html
+++ b/templates/main.html
@@ -9,6 +9,7 @@
+