Commit 20290d2b by Brian Jacobel

Convert display.js to javascript, preserving comments

parent be18f9e9
class @Sequence // Generated by CoffeeScript 1.6.1
constructor: (element) -> (function() {
@updatedProblems = {} var _this = this;
@requestToken = $(element).data('request-token')
@el = $(element).find('.sequence') this.Sequence = (function() {
@path = $('.path')
@contents = @$('.seq_contents') function Sequence(element) {
@content_container = @$('#seq_content') var _this = this;
@sr_container = @$('.sr-is-focusable') this.removeBookmarkIconFromActiveNavItem = function(event) {
@num_contents = @contents.length return Sequence.prototype.removeBookmarkIconFromActiveNavItem.apply(_this, arguments);
@id = @el.data('id') };
@ajaxUrl = @el.data('ajax-url') this.addBookmarkIconToActiveNavItem = function(event) {
@nextUrl = @el.data('next-url') return Sequence.prototype.addBookmarkIconToActiveNavItem.apply(_this, arguments);
@prevUrl = @el.data('prev-url') };
@base_page_title = " | " + document.title this._change_sequential = function(direction, event) {
@initProgress() return Sequence.prototype._change_sequential.apply(_this, arguments);
@bind() };
@render parseInt(@el.data('position')) this.selectPrevious = function(event) {
return Sequence.prototype.selectPrevious.apply(_this, arguments);
$: (selector) -> };
$(selector, @el) this.selectNext = function(event) {
return Sequence.prototype.selectNext.apply(_this, arguments);
bind: -> };
@$('#sequence-list .nav-item').click @goto this.goto = function(event) {
@el.on 'bookmark:add', @addBookmarkIconToActiveNavItem return Sequence.prototype.goto.apply(_this, arguments);
@el.on 'bookmark:remove', @removeBookmarkIconFromActiveNavItem };
@$('#sequence-list .nav-item').on('focus mouseenter', @displayTabTooltip) this.toggleArrows = function() {
@$('#sequence-list .nav-item').on('blur mouseleave', @hideTabTooltip) return Sequence.prototype.toggleArrows.apply(_this, arguments);
};
displayTabTooltip: (event) => this.updateProgress = function() {
$(event.currentTarget).find('.sequence-tooltip').removeClass('sr') return Sequence.prototype.updateProgress.apply(_this, arguments);
};
hideTabTooltip: (event) => this.addToUpdatedProblems = function(problem_id, new_content_state, new_state) {
$(event.currentTarget).find('.sequence-tooltip').addClass('sr') return Sequence.prototype.addToUpdatedProblems.apply(_this, arguments);
};
initProgress: -> this.hideTabTooltip = function(event) {
@progressTable = {} # "#problem_#{id}" -> progress return Sequence.prototype.hideTabTooltip.apply(_this, arguments);
};
updatePageTitle: -> this.displayTabTooltip = function(event) {
# update the page title to include the current section return Sequence.prototype.displayTabTooltip.apply(_this, arguments);
position_link = @link_for(@position) };
if position_link and position_link.data('page-title') this.updatedProblems = {};
document.title = position_link.data('page-title') + @base_page_title this.requestToken = $(element).data('request-token');
this.el = $(element).find('.sequence');
hookUpContentStateChangeEvent: -> this.path = $('.path');
$('.problems-wrapper').bind( this.contents = this.$('.seq_contents');
'contentChanged', this.content_container = this.$('#seq_content');
(event, problem_id, new_content_state, new_state) => this.sr_container = this.$('.sr-is-focusable');
@addToUpdatedProblems problem_id, new_content_state, new_state this.num_contents = this.contents.length;
) this.id = this.el.data('id');
this.ajaxUrl = this.el.data('ajax-url');
addToUpdatedProblems: (problem_id, new_content_state, new_state) => this.nextUrl = this.el.data('next-url');
# Used to keep updated problem's state temporarily. this.prevUrl = this.el.data('prev-url');
# params: this.base_page_title = " | " + document.title;
# 'problem_id' is problem id. this.initProgress();
# 'new_content_state' is the updated content of the problem. this.bind();
# 'new_state' is the updated state of the problem. this.render(parseInt(this.el.data('position')));
}
# initialize for the current sequence if there isn't any updated problem
# for this position. Sequence.prototype.$ = function(selector) {
if not @anyUpdatedProblems @position return $(selector, this.el);
@updatedProblems[@position] = {} };
# Now, put problem content and score against problem id for current active sequence. Sequence.prototype.bind = function() {
@updatedProblems[@position][problem_id] = [new_content_state, new_state] this.$('#sequence-list .nav-item').click(this.goto);
this.el.on('bookmark:add', this.addBookmarkIconToActiveNavItem);
anyUpdatedProblems:(position) -> this.el.on('bookmark:remove', this.removeBookmarkIconFromActiveNavItem);
# check for the updated problems for given sequence position. this.$('#sequence-list .nav-item').on('focus mouseenter', this.displayTabTooltip);
# params: return this.$('#sequence-list .nav-item').on('blur mouseleave', this.hideTabTooltip);
# 'position' can be any sequence position. };
return @updatedProblems[position] != undefined
Sequence.prototype.displayTabTooltip = function(event) {
hookUpProgressEvent: -> return $(event.currentTarget).find('.sequence-tooltip').removeClass('sr');
$('.problems-wrapper').bind 'progressChanged', @updateProgress };
mergeProgress: (p1, p2) -> Sequence.prototype.hideTabTooltip = function(event) {
# if either is "NA", return the other one return $(event.currentTarget).find('.sequence-tooltip').addClass('sr');
if p1 == "NA" };
return p2
if p2 == "NA" Sequence.prototype.initProgress = function() {
return p1 /*
"#problem_#{id}" -> progress
# Both real progresses */
if p1 == "done" and p2 == "done" return this.progressTable = {};
return "done" };
# not done, so if any progress on either, in_progress Sequence.prototype.updatePageTitle = function() {
w1 = p1 == "done" or p1 == "in_progress" /*
w2 = p2 == "done" or p2 == "in_progress" update the page title to include the current section
if w1 or w2 */
return "in_progress"
var position_link;
return "none" position_link = this.link_for(this.position);
if (position_link && position_link.data('page-title')) {
updateProgress: => return document.title = position_link.data('page-title') + this.base_page_title;
new_progress = "NA" }
_this = this };
$('.problems-wrapper').each (index) ->
progress = $(this).data 'progress_status' Sequence.prototype.hookUpContentStateChangeEvent = function() {
new_progress = _this.mergeProgress progress, new_progress var _this = this;
return $('.problems-wrapper').bind('contentChanged', function(event, problem_id, new_content_state, new_state) {
@progressTable[@position] = new_progress return _this.addToUpdatedProblems(problem_id, new_content_state, new_state);
});
enableButton: (button_class, button_action) -> };
@$(button_class).removeClass('disabled').removeAttr('disabled').click(button_action)
Sequence.prototype.addToUpdatedProblems = function(problem_id, new_content_state, new_state) {
disableButton: (button_class) -> /*
@$(button_class).addClass('disabled').attr('disabled', true) Used to keep updated problem's state temporarily.
*/
setButtonLabel: (button_class, button_label) ->
@$(button_class + ' .sr').html(button_label) /*
params:
updateButtonState: (button_class, button_action, action_label_prefix, is_at_boundary, boundary_url) -> */
if is_at_boundary and boundary_url == 'None'
@disableButton(button_class) /*
else 'problem_id' is problem id.
button_label = action_label_prefix + (if is_at_boundary then ' Subsection' else ' Unit') */
@setButtonLabel(button_class, button_label)
@enableButton(button_class, button_action) /*
'new_content_state' is the updated content of the problem.
toggleArrows: => */
@$('.sequence-nav-button').unbind('click')
/*
# previous button 'new_state' is the updated state of the problem.
is_first_tab = @position == 1 */
previous_button_class = '.sequence-nav-button.button-previous'
@updateButtonState( /*
previous_button_class, initialize for the current sequence if there isn't any updated problem
@selectPrevious, */
'Previous',
is_first_tab, /*
@prevUrl for this position.
) */
if (!this.anyUpdatedProblems(this.position)) {
# next button this.updatedProblems[this.position] = {};
is_last_tab = @position >= @contents.length # use inequality in case contents.length is 0 and position is 1. }
next_button_class = '.sequence-nav-button.button-next' /*
@updateButtonState( Now, put problem content and score against problem id for current active sequence.
next_button_class, */
@selectNext,
'Next', return this.updatedProblems[this.position][problem_id] = [new_content_state, new_state];
is_last_tab, };
@nextUrl
) Sequence.prototype.anyUpdatedProblems = function(position) {
/*
render: (new_position) -> check for the updated problems for given sequence position.
if @position != new_position */
if @position != undefined
@mark_visited @position /*
modx_full_url = "#{@ajaxUrl}/goto_position" params:
$.postWithPrefix modx_full_url, position: new_position */
# On Sequence change, fire custom event "sequence:change" on element. /*
# Added for aborting video bufferization, see ../video/10_main.js 'position' can be any sequence position.
@el.trigger "sequence:change" */
@mark_active new_position return this.updatedProblems[position] !== void 0;
};
current_tab = @contents.eq(new_position - 1)
Sequence.prototype.hookUpProgressEvent = function() {
bookmarked = if @el.find('.active .bookmark-icon').hasClass('bookmarked') then true else false return $('.problems-wrapper').bind('progressChanged', this.updateProgress);
@content_container.html(current_tab.text()).attr("aria-labelledby", current_tab.attr("aria-labelledby")).data('bookmarked', bookmarked) };
# update the data-attributes with latest contents only for updated problems. Sequence.prototype.mergeProgress = function(p1, p2) {
if @anyUpdatedProblems new_position /*
$.each @updatedProblems[new_position], (problem_id, latest_data) => if either is "NA", return the other one
latest_content = latest_data[0] */
latest_response = latest_data[1]
@content_container var w1, w2;
.find("[data-problem-id='#{ problem_id }']") if (p1 === "NA") {
.data('content', latest_content) return p2;
.data('problem-score', latest_response.current_score) }
.data('problem-total-possible', latest_response.total_possible) if (p2 === "NA") {
.data('attempts-used', latest_response.attempts_used) return p1;
}
XBlock.initializeBlocks(@content_container, @requestToken) /*
Both real progresses
window.update_schematics() # For embedded circuit simulator exercises in 6.002x */
@position = new_position if (p1 === "done" && p2 === "done") {
@toggleArrows() return "done";
@hookUpContentStateChangeEvent() }
@hookUpProgressEvent() /*
@updatePageTitle() not done, so if any progress on either, in_progress
*/
sequence_links = @content_container.find('a.seqnav')
sequence_links.click @goto w1 = p1 === "done" || p1 === "in_progress";
w2 = p2 === "done" || p2 === "in_progress";
@path.text(@el.find('.nav-item.active').data('path')) if (w1 || w2) {
return "in_progress";
@sr_container.focus() }
return "none";
goto: (event) => };
event.preventDefault()
if $(event.currentTarget).hasClass 'seqnav' # Links from courseware <a class='seqnav' href='n'>...</a>, was .target Sequence.prototype.updateProgress = function() {
new_position = $(event.currentTarget).attr('href') var new_progress;
else # Tab links generated by backend template new_progress = "NA";
new_position = $(event.currentTarget).data('element') _this = this;
$('.problems-wrapper').each(function(index) {
if (1 <= new_position) and (new_position <= @num_contents) var progress;
is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0 progress = $(this).data('progress_status');
if is_bottom_nav return new_progress = _this.mergeProgress(progress, new_progress);
widget_placement = 'bottom' });
else return this.progressTable[this.position] = new_progress;
widget_placement = 'top' };
Logger.log "edx.ui.lms.sequence.tab_selected", # Formerly known as seq_goto
current_tab: @position Sequence.prototype.enableButton = function(button_class, button_action) {
target_tab: new_position return this.$(button_class).removeClass('disabled').removeAttr('disabled').click(button_action);
tab_count: @num_contents };
id: @id
Sequence.prototype.disableButton = function(button_class) {
return this.$(button_class).addClass('disabled').attr('disabled', true);
};
Sequence.prototype.setButtonLabel = function(button_class, button_label) {
return this.$(button_class + ' .sr').html(button_label);
};
Sequence.prototype.updateButtonState = function(button_class, button_action, action_label_prefix, is_at_boundary, boundary_url) {
var button_label;
if (is_at_boundary && boundary_url === 'None') {
return this.disableButton(button_class);
} else {
button_label = action_label_prefix + (is_at_boundary ? ' Subsection' : ' Unit');
this.setButtonLabel(button_class, button_label);
return this.enableButton(button_class, button_action);
}
};
Sequence.prototype.toggleArrows = function() {
var is_first_tab, is_last_tab, next_button_class, previous_button_class;
this.$('.sequence-nav-button').unbind('click');
/*
previous button
*/
is_first_tab = this.position === 1;
previous_button_class = '.sequence-nav-button.button-previous';
this.updateButtonState(previous_button_class, this.selectPrevious, 'Previous', is_first_tab, this.prevUrl);
/*
next button
*/
/*
use inequality in case contents.length is 0 and position is 1.
*/
is_last_tab = this.position >= this.contents.length;
next_button_class = '.sequence-nav-button.button-next';
return this.updateButtonState(next_button_class, this.selectNext, 'Next', is_last_tab, this.nextUrl);
};
Sequence.prototype.render = function(new_position) {
var bookmarked, current_tab, modx_full_url, sequence_links,
_this = this;
if (this.position !== new_position) {
if (this.position !== void 0) {
this.mark_visited(this.position);
modx_full_url = "" + this.ajaxUrl + "/goto_position";
$.postWithPrefix(modx_full_url, {
position: new_position
});
}
/*
On Sequence change, fire custom event "sequence:change" on element.
*/
/*
Added for aborting video bufferization, see ../video/10_main.js
*/
this.el.trigger("sequence:change");
this.mark_active(new_position);
current_tab = this.contents.eq(new_position - 1);
bookmarked = this.el.find('.active .bookmark-icon').hasClass('bookmarked') ? true : false;
this.content_container.html(current_tab.text()).attr("aria-labelledby", current_tab.attr("aria-labelledby")).data('bookmarked', bookmarked);
/*
update the data-attributes with latest contents only for updated problems.
*/
if (this.anyUpdatedProblems(new_position)) {
$.each(this.updatedProblems[new_position], function(problem_id, latest_data) {
var latest_content, latest_response;
latest_content = latest_data[0];
latest_response = latest_data[1];
return _this.content_container.find("[data-problem-id='" + problem_id + "']").data('content', latest_content).data('problem-score', latest_response.current_score).data('problem-total-possible', latest_response.total_possible).data('attempts-used', latest_response.attempts_used);
});
}
XBlock.initializeBlocks(this.content_container, this.requestToken);
/*
For embedded circuit simulator exercises in 6.002x
*/
window.update_schematics();
this.position = new_position;
this.toggleArrows();
this.hookUpContentStateChangeEvent();
this.hookUpProgressEvent();
this.updatePageTitle();
sequence_links = this.content_container.find('a.seqnav');
sequence_links.click(this.goto);
this.path.text(this.el.find('.nav-item.active').data('path'));
return this.sr_container.focus();
}
};
Sequence.prototype.goto = function(event) {
var alert_template, alert_text, is_bottom_nav, new_position, widget_placement;
event.preventDefault();
/*
Links from courseware <a class='seqnav' href='n'>...</a>, was .target
*/
if ($(event.currentTarget).hasClass('seqnav')) {
new_position = $(event.currentTarget).attr('href');
/*
Tab links generated by backend template
*/
} else {
new_position = $(event.currentTarget).data('element');
}
if ((1 <= new_position) && (new_position <= this.num_contents)) {
is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0;
if (is_bottom_nav) {
widget_placement = 'bottom';
} else {
widget_placement = 'top';
}
/*
Formerly known as seq_goto
*/
Logger.log("edx.ui.lms.sequence.tab_selected", {
current_tab: this.position,
target_tab: new_position,
tab_count: this.num_contents,
id: this.id,
widget_placement: widget_placement widget_placement: widget_placement
});
# On Sequence change, destroy any existing polling thread /*
# for queued submissions, see ../capa/display.js On Sequence change, destroy any existing polling thread
if window.queuePollerID */
window.clearTimeout(window.queuePollerID)
delete window.queuePollerID /*
for queued submissions, see ../capa/display.js
@render new_position */
else
alert_template = gettext("Sequence error! Cannot navigate to %(tab_name)s in the current SequenceModule. Please contact the course staff.") if (window.queuePollerID) {
alert_text = interpolate(alert_template, {tab_name: new_position}, true) window.clearTimeout(window.queuePollerID);
alert alert_text delete window.queuePollerID;
}
selectNext: (event) => @_change_sequential 'next', event return this.render(new_position);
} else {
selectPrevious: (event) => @_change_sequential 'previous', event alert_template = gettext("Sequence error! Cannot navigate to %(tab_name)s in the current SequenceModule. Please contact the course staff.");
alert_text = interpolate(alert_template, {
# `direction` can be 'previous' or 'next' tab_name: new_position
_change_sequential: (direction, event) => }, true);
# silently abort if direction is invalid. return alert(alert_text);
return unless direction in ['previous', 'next'] }
};
event.preventDefault()
Sequence.prototype.selectNext = function(event) {
analytics_event_name = "edx.ui.lms.sequence.#{direction}_selected" return this._change_sequential('next', event);
is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0 };
if is_bottom_nav Sequence.prototype.selectPrevious = function(event) {
widget_placement = 'bottom' return this._change_sequential('previous', event);
else };
widget_placement = 'top'
/*
Logger.log analytics_event_name, # Formerly known as seq_next and seq_prev `direction` can be 'previous' or 'next'
id: @id */
current_tab: @position
tab_count: @num_contents
Sequence.prototype._change_sequential = function(direction, event) {
/*
silently abort if direction is invalid.
*/
var analytics_event_name, is_bottom_nav, new_position, offset, widget_placement;
if (direction !== 'previous' && direction !== 'next') {
return;
}
event.preventDefault();
analytics_event_name = "edx.ui.lms.sequence." + direction + "_selected";
is_bottom_nav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0;
if (is_bottom_nav) {
widget_placement = 'bottom';
} else {
widget_placement = 'top';
}
/*
Formerly known as seq_next and seq_prev
*/
Logger.log(analytics_event_name, {
id: this.id,
current_tab: this.position,
tab_count: this.num_contents,
widget_placement: widget_placement widget_placement: widget_placement
});
if (direction == 'next') and (@position >= @contents.length) if ((direction === 'next') && (this.position >= this.contents.length)) {
window.location.href = @nextUrl return window.location.href = this.nextUrl;
else if (direction == 'previous') and (@position == 1) } else if ((direction === 'previous') && (this.position === 1)) {
window.location.href = @prevUrl return window.location.href = this.prevUrl;
else } else {
# If the bottom nav is used, scroll to the top of the page on change. /*
if is_bottom_nav If the bottom nav is used, scroll to the top of the page on change.
$.scrollTo 0, 150 */
offset =
next: 1 if (is_bottom_nav) {
$.scrollTo(0, 150);
}
offset = {
next: 1,
previous: -1 previous: -1
new_position = @position + offset[direction] };
@render new_position new_position = this.position + offset[direction];
return this.render(new_position);
link_for: (position) -> }
@$("#sequence-list .nav-item[data-element=#{position}]") };
mark_visited: (position) -> Sequence.prototype.link_for = function(position) {
# Don't overwrite class attribute to avoid changing Progress class return this.$("#sequence-list .nav-item[data-element=" + position + "]");
element = @link_for(position) };
element.removeClass("inactive")
.removeClass("active") Sequence.prototype.mark_visited = function(position) {
.addClass("visited") /*
Don't overwrite class attribute to avoid changing Progress class
mark_active: (position) -> */
# Don't overwrite class attribute to avoid changing Progress class
element = @link_for(position) var element;
element.removeClass("inactive") element = this.link_for(position);
.removeClass("visited") return element.removeClass("inactive").removeClass("active").addClass("visited");
.addClass("active") };
addBookmarkIconToActiveNavItem: (event) => Sequence.prototype.mark_active = function(position) {
event.preventDefault() /*
@el.find('.nav-item.active .bookmark-icon').removeClass('is-hidden').addClass('bookmarked') Don't overwrite class attribute to avoid changing Progress class
@el.find('.nav-item.active .bookmark-icon-sr').text(gettext('Bookmarked')) */
removeBookmarkIconFromActiveNavItem: (event) => var element;
event.preventDefault() element = this.link_for(position);
@el.find('.nav-item.active .bookmark-icon').removeClass('bookmarked').addClass('is-hidden') return element.removeClass("inactive").removeClass("visited").addClass("active");
@el.find('.nav-item.active .bookmark-icon-sr').text('') };
Sequence.prototype.addBookmarkIconToActiveNavItem = function(event) {
event.preventDefault();
this.el.find('.nav-item.active .bookmark-icon').removeClass('is-hidden').addClass('bookmarked');
return this.el.find('.nav-item.active .bookmark-icon-sr').text(gettext('Bookmarked'));
};
Sequence.prototype.removeBookmarkIconFromActiveNavItem = function(event) {
event.preventDefault();
this.el.find('.nav-item.active .bookmark-icon').removeClass('bookmarked').addClass('is-hidden');
return this.el.find('.nav-item.active .bookmark-icon-sr').text('');
};
return Sequence;
})();
}).call(this);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment