Merge pull request #14167 from edx/bjacobel/sequence-coffee-to-js
Move Sequence coffee files to JS
This commit is contained in:
@@ -52,7 +52,6 @@ common/lib/xmodule/xmodule/js/src/html/edit.js
|
||||
common/lib/xmodule/xmodule/js/src/raw/edit/json.js
|
||||
common/lib/xmodule/xmodule/js/src/raw/edit/metadata-only.js
|
||||
common/lib/xmodule/xmodule/js/src/raw/edit/xml.js
|
||||
common/lib/xmodule/xmodule/js/src/sequence/display.js
|
||||
common/lib/xmodule/xmodule/js/src/sequence/edit.js
|
||||
common/lib/xmodule/xmodule/js/src/tabs/tabs-aggregator.js
|
||||
common/lib/xmodule/xmodule/js/src/vertical/edit.js
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
class @Sequence
|
||||
constructor: (element) ->
|
||||
@updatedProblems = {}
|
||||
@requestToken = $(element).data('request-token')
|
||||
@el = $(element).find('.sequence')
|
||||
@path = $('.path')
|
||||
@contents = @$('.seq_contents')
|
||||
@content_container = @$('#seq_content')
|
||||
@sr_container = @$('.sr-is-focusable')
|
||||
@num_contents = @contents.length
|
||||
@id = @el.data('id')
|
||||
@ajaxUrl = @el.data('ajax-url')
|
||||
@nextUrl = @el.data('next-url')
|
||||
@prevUrl = @el.data('prev-url')
|
||||
@base_page_title = " | " + document.title
|
||||
@initProgress()
|
||||
@bind()
|
||||
@render parseInt(@el.data('position'))
|
||||
|
||||
$: (selector) ->
|
||||
$(selector, @el)
|
||||
|
||||
bind: ->
|
||||
@$('#sequence-list .nav-item').click @goto
|
||||
@el.on 'bookmark:add', @addBookmarkIconToActiveNavItem
|
||||
@el.on 'bookmark:remove', @removeBookmarkIconFromActiveNavItem
|
||||
@$('#sequence-list .nav-item').on('focus mouseenter', @displayTabTooltip)
|
||||
@$('#sequence-list .nav-item').on('blur mouseleave', @hideTabTooltip)
|
||||
|
||||
displayTabTooltip: (event) =>
|
||||
$(event.currentTarget).find('.sequence-tooltip').removeClass('sr')
|
||||
|
||||
hideTabTooltip: (event) =>
|
||||
$(event.currentTarget).find('.sequence-tooltip').addClass('sr')
|
||||
|
||||
initProgress: ->
|
||||
@progressTable = {} # "#problem_#{id}" -> progress
|
||||
|
||||
updatePageTitle: ->
|
||||
# update the page title to include the current section
|
||||
position_link = @link_for(@position)
|
||||
if position_link and position_link.data('page-title')
|
||||
document.title = position_link.data('page-title') + @base_page_title
|
||||
|
||||
hookUpContentStateChangeEvent: ->
|
||||
$('.problems-wrapper').bind(
|
||||
'contentChanged',
|
||||
(event, problem_id, new_content_state, new_state) =>
|
||||
@addToUpdatedProblems problem_id, new_content_state, new_state
|
||||
)
|
||||
|
||||
addToUpdatedProblems: (problem_id, new_content_state, new_state) =>
|
||||
# Used to keep updated problem's state temporarily.
|
||||
# params:
|
||||
# 'problem_id' is problem id.
|
||||
# 'new_content_state' is the updated content of the problem.
|
||||
# 'new_state' is the updated state of the problem.
|
||||
|
||||
# initialize for the current sequence if there isn't any updated problem
|
||||
# for this position.
|
||||
if not @anyUpdatedProblems @position
|
||||
@updatedProblems[@position] = {}
|
||||
|
||||
# Now, put problem content and score against problem id for current active sequence.
|
||||
@updatedProblems[@position][problem_id] = [new_content_state, new_state]
|
||||
|
||||
anyUpdatedProblems:(position) ->
|
||||
# check for the updated problems for given sequence position.
|
||||
# params:
|
||||
# 'position' can be any sequence position.
|
||||
return @updatedProblems[position] != undefined
|
||||
|
||||
hookUpProgressEvent: ->
|
||||
$('.problems-wrapper').bind 'progressChanged', @updateProgress
|
||||
|
||||
mergeProgress: (p1, p2) ->
|
||||
# if either is "NA", return the other one
|
||||
if p1 == "NA"
|
||||
return p2
|
||||
if p2 == "NA"
|
||||
return p1
|
||||
|
||||
# Both real progresses
|
||||
if p1 == "done" and p2 == "done"
|
||||
return "done"
|
||||
|
||||
# not done, so if any progress on either, in_progress
|
||||
w1 = p1 == "done" or p1 == "in_progress"
|
||||
w2 = p2 == "done" or p2 == "in_progress"
|
||||
if w1 or w2
|
||||
return "in_progress"
|
||||
|
||||
return "none"
|
||||
|
||||
updateProgress: =>
|
||||
new_progress = "NA"
|
||||
_this = this
|
||||
$('.problems-wrapper').each (index) ->
|
||||
progress = $(this).data 'progress_status'
|
||||
new_progress = _this.mergeProgress progress, new_progress
|
||||
|
||||
@progressTable[@position] = new_progress
|
||||
|
||||
enableButton: (button_class, button_action) ->
|
||||
@$(button_class).removeClass('disabled').removeAttr('disabled').click(button_action)
|
||||
|
||||
disableButton: (button_class) ->
|
||||
@$(button_class).addClass('disabled').attr('disabled', true)
|
||||
|
||||
setButtonLabel: (button_class, button_label) ->
|
||||
@$(button_class + ' .sr').html(button_label)
|
||||
|
||||
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
|
||||
button_label = action_label_prefix + (if is_at_boundary then ' Subsection' else ' Unit')
|
||||
@setButtonLabel(button_class, button_label)
|
||||
@enableButton(button_class, button_action)
|
||||
|
||||
toggleArrows: =>
|
||||
@$('.sequence-nav-button').unbind('click')
|
||||
|
||||
# previous button
|
||||
is_first_tab = @position == 1
|
||||
previous_button_class = '.sequence-nav-button.button-previous'
|
||||
@updateButtonState(
|
||||
previous_button_class, # bound element
|
||||
@selectPrevious, # action
|
||||
'Previous', # label prefix
|
||||
is_first_tab, # is boundary?
|
||||
@prevUrl # boundary_url
|
||||
)
|
||||
|
||||
# next button
|
||||
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(
|
||||
next_button_class, # bound element
|
||||
@selectNext, # action
|
||||
'Next', # label prefix
|
||||
is_last_tab, # is boundary?
|
||||
@nextUrl # boundary_url
|
||||
)
|
||||
|
||||
render: (new_position) ->
|
||||
if @position != new_position
|
||||
if @position != undefined
|
||||
@mark_visited @position
|
||||
modx_full_url = "#{@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
|
||||
@el.trigger "sequence:change"
|
||||
@mark_active new_position
|
||||
|
||||
current_tab = @contents.eq(new_position - 1)
|
||||
|
||||
bookmarked = if @el.find('.active .bookmark-icon').hasClass('bookmarked') then true else false
|
||||
@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 @anyUpdatedProblems new_position
|
||||
$.each @updatedProblems[new_position], (problem_id, latest_data) =>
|
||||
latest_content = latest_data[0]
|
||||
latest_response = latest_data[1]
|
||||
@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(@content_container, @requestToken)
|
||||
|
||||
window.update_schematics() # For embedded circuit simulator exercises in 6.002x
|
||||
|
||||
@position = new_position
|
||||
@toggleArrows()
|
||||
@hookUpContentStateChangeEvent()
|
||||
@hookUpProgressEvent()
|
||||
@updatePageTitle()
|
||||
|
||||
sequence_links = @content_container.find('a.seqnav')
|
||||
sequence_links.click @goto
|
||||
|
||||
@path.text(@el.find('.nav-item.active').data('path'))
|
||||
|
||||
@sr_container.focus()
|
||||
|
||||
goto: (event) =>
|
||||
event.preventDefault()
|
||||
if $(event.currentTarget).hasClass 'seqnav' # Links from courseware <a class='seqnav' href='n'>...</a>, was .target
|
||||
new_position = $(event.currentTarget).attr('href')
|
||||
else # Tab links generated by backend template
|
||||
new_position = $(event.currentTarget).data('element')
|
||||
|
||||
if (1 <= new_position) and (new_position <= @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'
|
||||
Logger.log "edx.ui.lms.sequence.tab_selected", # Formerly known as seq_goto
|
||||
current_tab: @position
|
||||
target_tab: new_position
|
||||
tab_count: @num_contents
|
||||
id: @id
|
||||
widget_placement: widget_placement
|
||||
|
||||
# On Sequence change, destroy any existing polling thread
|
||||
# for queued submissions, see ../capa/display.js
|
||||
if window.queuePollerID
|
||||
window.clearTimeout(window.queuePollerID)
|
||||
delete window.queuePollerID
|
||||
|
||||
@render new_position
|
||||
else
|
||||
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, {tab_name: new_position}, true)
|
||||
alert alert_text
|
||||
|
||||
selectNext: (event) => @_change_sequential 'next', event
|
||||
|
||||
selectPrevious: (event) => @_change_sequential 'previous', event
|
||||
|
||||
# `direction` can be 'previous' or 'next'
|
||||
_change_sequential: (direction, event) =>
|
||||
# silently abort if direction is invalid.
|
||||
return unless direction in ['previous', 'next']
|
||||
|
||||
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'
|
||||
|
||||
Logger.log analytics_event_name, # Formerly known as seq_next and seq_prev
|
||||
id: @id
|
||||
current_tab: @position
|
||||
tab_count: @num_contents
|
||||
widget_placement: widget_placement
|
||||
|
||||
if (direction == 'next') and (@position >= @contents.length)
|
||||
window.location.href = @nextUrl
|
||||
else if (direction == 'previous') and (@position == 1)
|
||||
window.location.href = @prevUrl
|
||||
else
|
||||
# If the bottom nav is used, scroll to the top of the page on change.
|
||||
if is_bottom_nav
|
||||
$.scrollTo 0, 150
|
||||
offset =
|
||||
next: 1
|
||||
previous: -1
|
||||
new_position = @position + offset[direction]
|
||||
@render new_position
|
||||
|
||||
link_for: (position) ->
|
||||
@$("#sequence-list .nav-item[data-element=#{position}]")
|
||||
|
||||
mark_visited: (position) ->
|
||||
# Don't overwrite class attribute to avoid changing Progress class
|
||||
element = @link_for(position)
|
||||
element.removeClass("inactive")
|
||||
.removeClass("active")
|
||||
.addClass("visited")
|
||||
|
||||
mark_active: (position) ->
|
||||
# Don't overwrite class attribute to avoid changing Progress class
|
||||
element = @link_for(position)
|
||||
element.removeClass("inactive")
|
||||
.removeClass("visited")
|
||||
.addClass("active")
|
||||
|
||||
addBookmarkIconToActiveNavItem: (event) =>
|
||||
event.preventDefault()
|
||||
@el.find('.nav-item.active .bookmark-icon').removeClass('is-hidden').addClass('bookmarked')
|
||||
@el.find('.nav-item.active .bookmark-icon-sr').text(gettext('Bookmarked'))
|
||||
|
||||
removeBookmarkIconFromActiveNavItem: (event) =>
|
||||
event.preventDefault()
|
||||
@el.find('.nav-item.active .bookmark-icon').removeClass('bookmarked').addClass('is-hidden')
|
||||
@el.find('.nav-item.active .bookmark-icon-sr').text('')
|
||||
343
common/lib/xmodule/xmodule/js/src/sequence/display.js
Normal file
343
common/lib/xmodule/xmodule/js/src/sequence/display.js
Normal file
@@ -0,0 +1,343 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
/* globals Logger, interpolate */
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
this.Sequence = (function() {
|
||||
function Sequence(element) {
|
||||
var self = this;
|
||||
|
||||
this.removeBookmarkIconFromActiveNavItem = function(event) {
|
||||
return Sequence.prototype.removeBookmarkIconFromActiveNavItem.apply(self, [event]);
|
||||
};
|
||||
this.addBookmarkIconToActiveNavItem = function(event) {
|
||||
return Sequence.prototype.addBookmarkIconToActiveNavItem.apply(self, [event]);
|
||||
};
|
||||
this._change_sequential = function(direction, event) {
|
||||
return Sequence.prototype._change_sequential.apply(self, [direction, event]);
|
||||
};
|
||||
this.selectPrevious = function(event) {
|
||||
return Sequence.prototype.selectPrevious.apply(self, [event]);
|
||||
};
|
||||
this.selectNext = function(event) {
|
||||
return Sequence.prototype.selectNext.apply(self, [event]);
|
||||
};
|
||||
this.goto = function(event) {
|
||||
return Sequence.prototype.goto.apply(self, [event]);
|
||||
};
|
||||
this.toggleArrows = function() {
|
||||
return Sequence.prototype.toggleArrows.apply(self);
|
||||
};
|
||||
this.addToUpdatedProblems = function(problemId, newContentState, newState) {
|
||||
return Sequence.prototype.addToUpdatedProblems.apply(self, [problemId, newContentState, newState]);
|
||||
};
|
||||
this.hideTabTooltip = function(event) {
|
||||
return Sequence.prototype.hideTabTooltip.apply(self, [event]);
|
||||
};
|
||||
this.displayTabTooltip = function(event) {
|
||||
return Sequence.prototype.displayTabTooltip.apply(self, [event]);
|
||||
};
|
||||
|
||||
this.updatedProblems = {};
|
||||
this.requestToken = $(element).data('request-token');
|
||||
this.el = $(element).find('.sequence');
|
||||
this.path = $('.path');
|
||||
this.contents = this.$('.seq_contents');
|
||||
this.content_container = this.$('#seq_content');
|
||||
this.sr_container = this.$('.sr-is-focusable');
|
||||
this.num_contents = this.contents.length;
|
||||
this.id = this.el.data('id');
|
||||
this.ajaxUrl = this.el.data('ajax-url');
|
||||
this.nextUrl = this.el.data('next-url');
|
||||
this.prevUrl = this.el.data('prev-url');
|
||||
this.base_page_title = ' | ' + document.title;
|
||||
this.bind();
|
||||
this.render(parseInt(this.el.data('position'), 10));
|
||||
}
|
||||
|
||||
Sequence.prototype.$ = function(selector) {
|
||||
return $(selector, this.el);
|
||||
};
|
||||
|
||||
Sequence.prototype.bind = function() {
|
||||
this.$('#sequence-list .nav-item').click(this.goto);
|
||||
this.el.on('bookmark:add', this.addBookmarkIconToActiveNavItem);
|
||||
this.el.on('bookmark:remove', this.removeBookmarkIconFromActiveNavItem);
|
||||
this.$('#sequence-list .nav-item').on('focus mouseenter', this.displayTabTooltip);
|
||||
this.$('#sequence-list .nav-item').on('blur mouseleave', this.hideTabTooltip);
|
||||
};
|
||||
|
||||
Sequence.prototype.displayTabTooltip = function(event) {
|
||||
$(event.currentTarget).find('.sequence-tooltip').removeClass('sr');
|
||||
};
|
||||
|
||||
Sequence.prototype.hideTabTooltip = function(event) {
|
||||
$(event.currentTarget).find('.sequence-tooltip').addClass('sr');
|
||||
};
|
||||
|
||||
Sequence.prototype.updatePageTitle = function() {
|
||||
// update the page title to include the current section
|
||||
var positionLink = this.link_for(this.position);
|
||||
|
||||
if (positionLink && positionLink.data('page-title')) {
|
||||
document.title = positionLink.data('page-title') + this.base_page_title;
|
||||
}
|
||||
};
|
||||
|
||||
Sequence.prototype.hookUpContentStateChangeEvent = function() {
|
||||
var self = this;
|
||||
|
||||
return $('.problems-wrapper').bind('contentChanged', function(event, problemId, newContentState, newState) {
|
||||
return self.addToUpdatedProblems(problemId, newContentState, newState);
|
||||
});
|
||||
};
|
||||
|
||||
Sequence.prototype.addToUpdatedProblems = function(problemId, newContentState, newState) {
|
||||
/**
|
||||
* Used to keep updated problem's state temporarily.
|
||||
* params:
|
||||
* 'problem_id' is problem id.
|
||||
* 'new_content_state' is the updated content of the problem.
|
||||
* 'new_state' is the updated state of the problem.
|
||||
*/
|
||||
|
||||
// initialize for the current sequence if there isn't any updated problem for this position.
|
||||
if (!this.anyUpdatedProblems(this.position)) {
|
||||
this.updatedProblems[this.position] = {};
|
||||
}
|
||||
|
||||
// Now, put problem content and score against problem id for current active sequence.
|
||||
this.updatedProblems[this.position][problemId] = [newContentState, newState];
|
||||
};
|
||||
|
||||
Sequence.prototype.anyUpdatedProblems = function(position) {
|
||||
/**
|
||||
* check for the updated problems for given sequence position.
|
||||
* params:
|
||||
* 'position' can be any sequence position.
|
||||
*/
|
||||
return typeof(this.updatedProblems[position]) !== 'undefined';
|
||||
};
|
||||
|
||||
Sequence.prototype.enableButton = function(buttonClass, buttonAction) {
|
||||
this.$(buttonClass)
|
||||
.removeClass('disabled')
|
||||
.removeAttr('disabled')
|
||||
.click(buttonAction);
|
||||
};
|
||||
|
||||
Sequence.prototype.disableButton = function(buttonClass) {
|
||||
this.$(buttonClass).addClass('disabled').attr('disabled', true);
|
||||
};
|
||||
|
||||
Sequence.prototype.updateButtonState = function(buttonClass, buttonAction, isAtBoundary, boundaryUrl) {
|
||||
if (isAtBoundary && boundaryUrl === 'None') {
|
||||
this.disableButton(buttonClass);
|
||||
} else {
|
||||
this.enableButton(buttonClass, buttonAction);
|
||||
}
|
||||
};
|
||||
|
||||
Sequence.prototype.toggleArrows = function() {
|
||||
var isFirstTab, isLastTab, nextButtonClass, previousButtonClass;
|
||||
|
||||
this.$('.sequence-nav-button').unbind('click');
|
||||
|
||||
// previous button
|
||||
isFirstTab = this.position === 1;
|
||||
previousButtonClass = '.sequence-nav-button.button-previous';
|
||||
this.updateButtonState(previousButtonClass, this.selectPrevious, isFirstTab, this.prevUrl);
|
||||
|
||||
// next button
|
||||
// use inequality in case contents.length is 0 and position is 1.
|
||||
isLastTab = this.position >= this.contents.length;
|
||||
nextButtonClass = '.sequence-nav-button.button-next';
|
||||
this.updateButtonState(nextButtonClass, this.selectNext, isLastTab, this.nextUrl);
|
||||
};
|
||||
|
||||
Sequence.prototype.render = function(newPosition) {
|
||||
var bookmarked, currentTab, modxFullUrl, sequenceLinks,
|
||||
self = this;
|
||||
if (this.position !== newPosition) {
|
||||
if (this.position) {
|
||||
this.mark_visited(this.position);
|
||||
modxFullUrl = '' + this.ajaxUrl + '/goto_position';
|
||||
$.postWithPrefix(modxFullUrl, {
|
||||
position: newPosition
|
||||
});
|
||||
}
|
||||
|
||||
// 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(newPosition);
|
||||
currentTab = this.contents.eq(newPosition - 1);
|
||||
bookmarked = this.el.find('.active .bookmark-icon').hasClass('bookmarked');
|
||||
|
||||
// update the data-attributes with latest contents only for updated problems.
|
||||
this.content_container
|
||||
.html(currentTab.text())
|
||||
.attr('aria-labelledby', currentTab.attr('aria-labelledby'))
|
||||
.data('bookmarked', bookmarked);
|
||||
|
||||
|
||||
if (this.anyUpdatedProblems(newPosition)) {
|
||||
$.each(this.updatedProblems[newPosition], function(problemId, latestData) {
|
||||
var latestContent, latestResponse;
|
||||
latestContent = latestData[0];
|
||||
latestResponse = latestData[1];
|
||||
self.content_container
|
||||
.find("[data-problem-id='" + problemId + "']")
|
||||
.data('content', latestContent)
|
||||
.data('problem-score', latestResponse.current_score)
|
||||
.data('problem-total-possible', latestResponse.total_possible)
|
||||
.data('attempts-used', latestResponse.attempts_used);
|
||||
});
|
||||
}
|
||||
XBlock.initializeBlocks(this.content_container, this.requestToken);
|
||||
|
||||
// For embedded circuit simulator exercises in 6.002x
|
||||
window.update_schematics();
|
||||
this.position = newPosition;
|
||||
this.toggleArrows();
|
||||
this.hookUpContentStateChangeEvent();
|
||||
this.updatePageTitle();
|
||||
sequenceLinks = this.content_container.find('a.seqnav');
|
||||
sequenceLinks.click(this.goto);
|
||||
this.path.text(this.el.find('.nav-item.active').data('path'));
|
||||
this.sr_container.focus();
|
||||
}
|
||||
};
|
||||
|
||||
Sequence.prototype.goto = function(event) {
|
||||
var alertTemplate, alertText, isBottomNav, newPosition, widgetPlacement;
|
||||
event.preventDefault();
|
||||
|
||||
// Links from courseware <a class='seqnav' href='n'>...</a>, was .target_tab
|
||||
if ($(event.currentTarget).hasClass('seqnav')) {
|
||||
newPosition = $(event.currentTarget).attr('href');
|
||||
// Tab links generated by backend template
|
||||
} else {
|
||||
newPosition = $(event.currentTarget).data('element');
|
||||
}
|
||||
|
||||
if ((newPosition >= 1) && (newPosition <= this.num_contents)) {
|
||||
isBottomNav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0;
|
||||
|
||||
if (isBottomNav) {
|
||||
widgetPlacement = 'bottom';
|
||||
} else {
|
||||
widgetPlacement = 'top';
|
||||
}
|
||||
|
||||
// Formerly known as seq_goto
|
||||
Logger.log('edx.ui.lms.sequence.tab_selected', {
|
||||
current_tab: this.position,
|
||||
target_tab: newPosition,
|
||||
tab_count: this.num_contents,
|
||||
id: this.id,
|
||||
widget_placement: widgetPlacement
|
||||
});
|
||||
|
||||
// On Sequence change, destroy any existing polling thread
|
||||
// for queued submissions, see ../capa/display.js
|
||||
if (window.queuePollerID) {
|
||||
window.clearTimeout(window.queuePollerID);
|
||||
delete window.queuePollerID;
|
||||
}
|
||||
this.render(newPosition);
|
||||
} else {
|
||||
alertTemplate = gettext('Sequence error! Cannot navigate to %(tab_name)s in the current SequenceModule. Please contact the course staff.'); // eslint-disable-line max-len
|
||||
alertText = interpolate(alertTemplate, {
|
||||
tab_name: newPosition
|
||||
}, true);
|
||||
alert(alertText); // eslint-disable-line no-alert
|
||||
}
|
||||
};
|
||||
|
||||
Sequence.prototype.selectNext = function(event) {
|
||||
this._change_sequential('next', event);
|
||||
};
|
||||
|
||||
Sequence.prototype.selectPrevious = function(event) {
|
||||
this._change_sequential('previous', event);
|
||||
};
|
||||
|
||||
// `direction` can be 'previous' or 'next'
|
||||
Sequence.prototype._change_sequential = function(direction, event) {
|
||||
var analyticsEventName, isBottomNav, newPosition, offset, widgetPlacement;
|
||||
|
||||
// silently abort if direction is invalid.
|
||||
if (direction !== 'previous' && direction !== 'next') {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
analyticsEventName = 'edx.ui.lms.sequence.' + direction + '_selected';
|
||||
isBottomNav = $(event.target).closest('nav[class="sequence-bottom"]').length > 0;
|
||||
|
||||
if (isBottomNav) {
|
||||
widgetPlacement = 'bottom';
|
||||
} else {
|
||||
widgetPlacement = 'top';
|
||||
}
|
||||
|
||||
// Formerly known as seq_next and seq_prev
|
||||
Logger.log(analyticsEventName, {
|
||||
id: this.id,
|
||||
current_tab: this.position,
|
||||
tab_count: this.num_contents,
|
||||
widget_placement: widgetPlacement
|
||||
});
|
||||
|
||||
if ((direction === 'next') && (this.position >= this.contents.length)) {
|
||||
window.location.href = this.nextUrl;
|
||||
} else if ((direction === 'previous') && (this.position === 1)) {
|
||||
window.location.href = this.prevUrl;
|
||||
} else {
|
||||
// If the bottom nav is used, scroll to the top of the page on change.
|
||||
if (isBottomNav) {
|
||||
$.scrollTo(0, 150);
|
||||
}
|
||||
|
||||
offset = {
|
||||
next: 1,
|
||||
previous: -1
|
||||
};
|
||||
|
||||
newPosition = this.position + offset[direction];
|
||||
this.render(newPosition);
|
||||
}
|
||||
};
|
||||
|
||||
Sequence.prototype.link_for = function(position) {
|
||||
return this.$('#sequence-list .nav-item[data-element=' + position + ']');
|
||||
};
|
||||
|
||||
Sequence.prototype.mark_visited = function(position) {
|
||||
// Don't overwrite class attribute to avoid changing Progress class
|
||||
var element = this.link_for(position);
|
||||
element.removeClass('inactive').removeClass('active').addClass('visited');
|
||||
};
|
||||
|
||||
Sequence.prototype.mark_active = function(position) {
|
||||
// Don't overwrite class attribute to avoid changing Progress class
|
||||
var element = this.link_for(position);
|
||||
element.removeClass('inactive').removeClass('visited').addClass('active');
|
||||
};
|
||||
|
||||
Sequence.prototype.addBookmarkIconToActiveNavItem = function(event) {
|
||||
event.preventDefault();
|
||||
this.el.find('.nav-item.active .bookmark-icon').removeClass('is-hidden').addClass('bookmarked');
|
||||
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');
|
||||
this.el.find('.nav-item.active .bookmark-icon-sr').text('');
|
||||
};
|
||||
|
||||
return Sequence;
|
||||
}());
|
||||
}).call(this);
|
||||
@@ -1 +0,0 @@
|
||||
!*.js
|
||||
@@ -1,74 +0,0 @@
|
||||
var SequenceNav = function($element) {
|
||||
var _this = this;
|
||||
var $element = $element;
|
||||
var $wrapper = $element.find('.sequence-list-wrapper');
|
||||
var $list = $element.find('#sequence-list');
|
||||
var $arrows = $element.find('.sequence-nav-button');
|
||||
var maxScroll = $list.width() - $wrapper.width();
|
||||
var $body = $('body');
|
||||
var listOrigin;
|
||||
var mouseOrigin;
|
||||
|
||||
var startDrag = function(e) {
|
||||
updateWidths();
|
||||
mouseOrigin = e.pageX;
|
||||
listOrigin = $list.position().left;
|
||||
$body.css('-webkit-user-select', 'none');
|
||||
$body.bind('mousemove', moveDrag);
|
||||
$body.bind('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
var moveDrag = function(e) {
|
||||
var offset = e.pageX - mouseOrigin;
|
||||
var targetLeft = clamp(listOrigin + offset, -maxScroll, 0);
|
||||
|
||||
updateHorizontalPosition(targetLeft);
|
||||
};
|
||||
|
||||
var stopDrag = function(e) {
|
||||
$body.css('-webkit-user-select', 'auto');
|
||||
$body.unbind('mousemove', moveDrag);
|
||||
$body.unbind('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
var clamp = function(val, min, max) {
|
||||
if(val > max) return max;
|
||||
if(val < min) return min;
|
||||
return val;
|
||||
};
|
||||
|
||||
var updateWidths = function(e) {
|
||||
maxScroll = $list.width() - $wrapper.width();
|
||||
var targetLeft = clamp($list.position().left, -maxScroll, 0);
|
||||
updateHorizontalPosition(targetLeft);
|
||||
};
|
||||
|
||||
var updateHorizontalPosition = function(left) {
|
||||
$list.css({
|
||||
'left': left + 'px'
|
||||
});
|
||||
};
|
||||
|
||||
var checkPosition = function(e) {
|
||||
var $active = $element.find('.active');
|
||||
if(!$active[0]) {
|
||||
return;
|
||||
}
|
||||
if($active.position().left + $active.width() > $wrapper.width() - $list.position().left) {
|
||||
$list.animate({
|
||||
'left': (-$active.position().left + $wrapper.width() - $active.width() - 10) + 'px'
|
||||
}, {});
|
||||
} else if($active.position().left < -$list.position().left) {
|
||||
$list.animate({
|
||||
'left': (-$active.position().left + 10) + 'px'
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
$wrapper.bind('mousedown', startDrag);
|
||||
$arrows.bind('click', checkPosition);
|
||||
$(window).bind('resize', updateWidths);
|
||||
setTimeout(function() {
|
||||
checkPosition();
|
||||
}, 200);
|
||||
};
|
||||
@@ -161,8 +161,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule):
|
||||
Layout module which lays out content in a temporal sequence
|
||||
"""
|
||||
js = {
|
||||
'coffee': [resource_string(__name__, 'js/src/sequence/display.coffee')],
|
||||
'js': [resource_string(__name__, 'js/src/sequence/display/jquery.sequence.js')],
|
||||
'js': [resource_string(__name__, 'js/src/sequence/display.js')],
|
||||
}
|
||||
css = {
|
||||
'scss': [resource_string(__name__, 'css/sequence/display.scss')],
|
||||
|
||||
@@ -64,10 +64,3 @@
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
var sequenceNav;
|
||||
$(document).ready(function() {
|
||||
sequenceNav = new SequenceNav($('.sequence-nav'));
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user