diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..421f3033 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "extends": "eslint-config-edx", + "globals": { + "videojs": true, + "domReady": true + }, + "rules": { + "no-underscore-dangle": [ + "error", {"allow": [ + // VideoJS components' attributes + "childNameIndex_", "el_", "kind_", "labelEl_", "options_", "player_" + ]} + ], + "require-jsdoc": ["error", { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true, + "ArrowFunctionExpression": false + } + }] + } +} diff --git a/.travis.yml b/.travis.yml index f55edac8..a81d1a70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,5 +2,13 @@ language: python python: - "2.7" +before_install: + # Install latest stable NodeJS version. Required for eslint. + - nvm install stable + +before_script: + - npm install eslint eslint-config-edx + script: - "pylint video_xblock" + - "eslint video_xblock/static/js/" diff --git a/video_xblock/static/js/brightcove-videojs-init.js b/video_xblock/static/js/brightcove-videojs-init.js index eef01d5f..34fb9106 100644 --- a/video_xblock/static/js/brightcove-videojs-init.js +++ b/video_xblock/static/js/brightcove-videojs-init.js @@ -1,15 +1,16 @@ -domReady(function(){ - window.videojs = videojs; - var player = videojs('{{ video_player_id }}'); - videojs.plugin('xblockEventPlugin', window.xblockEventPlugin); - player.xblockEventPlugin(); - videojs.plugin('offset', window.vjsoffset); +domReady(function() { + 'use strict'; + var player = videojs('{{ video_player_id }}'); + window.videojs = videojs; + videojs.plugin('xblockEventPlugin', window.xblockEventPlugin); + player.xblockEventPlugin(); + videojs.plugin('offset', window.vjsoffset); - player.offset({ - "start": 0, // do not use quotes for this properties for correct plugin work - "end": 0 - }); + player.offset({ + start: 0, // do not use quotes for this properties for correct plugin work + end: 0 + }); - videojs.plugin('videoJSSpeedHandler', window.videoJSSpeedHandler); - player.videoJSSpeedHandler(); + videojs.plugin('videoJSSpeedHandler', window.videoJSSpeedHandler); + player.videoJSSpeedHandler(); }); diff --git a/video_xblock/static/js/player-context-menu.js b/video_xblock/static/js/player-context-menu.js index f75a90ec..651b3118 100644 --- a/video_xblock/static/js/player-context-menu.js +++ b/video_xblock/static/js/player-context-menu.js @@ -12,132 +12,133 @@ * Initialise player context menu with nested elements. */ domReady(function() { + 'use strict'; + var videoPlayer = document.getElementById('{{ video_player_id }}'); + var dataSetup = JSON.parse(videoPlayer.getAttribute('data-setup')); + var playbackRates = dataSetup.playbackRates; + var docfrag = document.createDocumentFragment(); + // VideoJS Player() object necessary for context menu creation + var player = videojs('{{ video_player_id }}'); - var videoPlayer = document.getElementById("{{ video_player_id }}"); - var dataSetup = JSON.parse(videoPlayer.getAttribute('data-setup')); - var playbackRates = dataSetup.playbackRates; - var docfrag = document.createDocumentFragment(); - // VideoJS Player() object necessary for context menu creation - var player = videojs('{{ video_player_id }}'); - - /** - * Cross-browser wrapper for element.matches - * Source: https://gist.github.com/dalgard/7817372 - */ - function matchesSelector(dom_element, selector) { - var matchesSelector = - dom_element.matches || - dom_element.matchesSelector || - dom_element.webkitMatchesSelector || - dom_element.mozMatchesSelector || - dom_element.msMatchesSelector || - dom_element.oMatchesSelector; - return matchesSelector.call(dom_element, selector); - } - - /** - * Create elements of nested context submenu. - */ - function createNestedContextSubMenu(e) { - var target = e.target; - var labelElement = target.innerText; - var labelItem = getItem('speed').label; - - // Generate nested submenu elements as document fragment - var ulSubMenu = document.createElement('ul'); - ulSubMenu.className = 'vjs-contextmenu-ui-submenu'; - playbackRates.forEach(function(rate) { - var liSubMenu = document.createElement('li'); - liSubMenu.className = 'vjs-submenu-item'; - liSubMenu.innerHTML = rate + 'x'; - ulSubMenu.appendChild(liSubMenu); - liSubMenu.onclick = function() { - player.playbackRate(parseFloat(rate)); - }; - }); - docfrag.appendChild(ulSubMenu); - - // Check conditions to be met for delegation of the popup submenu creation - var menuItemClicked = matchesSelector(target, "li.vjs-menu-item"); - var noSubmenuClicked = !target.querySelector('.vjs-contextmenu-ui-submenu'); - var menuItemsLabelsEqual = (labelElement === labelItem); - - // Wrap into conditional statement to avoid unnecessary variables initialization - if (menuItemClicked && noSubmenuClicked) { - var labelLength = labelElement.length; - const lineFeedCode = 10; - // Check if the last character is an escaped one (line feed to get rid of) which is the case for Microsoft Edge - if (labelElement.charCodeAt(labelLength-1) === lineFeedCode) { - var labelElementSliced = labelElement.slice(0, -1); - menuItemsLabelsEqual = (labelElementSliced === labelItem); - } - } - - // Create nested submenu - if (menuItemClicked && noSubmenuClicked && menuItemsLabelsEqual){ - target.appendChild(docfrag); + /** + * Cross-browser wrapper for element.matches + * Source: https://gist.github.com/dalgard/7817372 + */ + function matchesSelector(domElement, selector) { + var matchesSelector = // eslint-disable-line no-shadow + domElement.matches || + domElement.matchesSelector || + domElement.webkitMatchesSelector || + domElement.mozMatchesSelector || + domElement.msMatchesSelector || + domElement.oMatchesSelector; + return matchesSelector.call(domElement, selector); } - } - // Delegate creation of a nested submenu for a context menu - videoPlayer.addEventListener('mouseover', createNestedContextSubMenu); + /** + * Create elements of nested context submenu. + */ + function createNestedContextSubMenu(e) { + var target = e.target; + var labelElement = target.innerText; + var labelItem = getItem('speed').label; // eslint-disable-line no-use-before-define + // Check conditions to be met for delegation of the popup submenu creation + var menuItemClicked = matchesSelector(target, 'li.vjs-menu-item'); + var noSubmenuClicked = !target.querySelector('.vjs-contextmenu-ui-submenu'); + var menuItemsLabelsEqual = (labelElement === labelItem); + // Generate nested submenu elements as document fragment + var ulSubMenu = document.createElement('ul'); + // Wrap into conditional statement to avoid unnecessary variables initialization + if (menuItemClicked && noSubmenuClicked) { + var labelLength = labelElement.length; // eslint-disable-line vars-on-top + var lineFeedCode = 10; // eslint-disable-line vars-on-top + // Check if the last character is an escaped one (line feed to get rid of) + // which is the case for Microsoft Edge + if (labelElement.charCodeAt(labelLength - 1) === lineFeedCode) { + var labelElementSliced = labelElement.slice(0, -1); // eslint-disable-line vars-on-top + menuItemsLabelsEqual = (labelElementSliced === labelItem); + } + } + ulSubMenu.className = 'vjs-contextmenu-ui-submenu'; + playbackRates.forEach(function(rate) { + var liSubMenu = document.createElement('li'); + liSubMenu.className = 'vjs-submenu-item'; + liSubMenu.innerHTML = rate + 'x'; + ulSubMenu.appendChild(liSubMenu); + liSubMenu.onclick = function() { + player.playbackRate(parseFloat(rate)); + }; + }); + docfrag.appendChild(ulSubMenu); - // Create context menu options - var content = [{ - id: "play", - label: 'Play', - listener: function () { - var item = getItem('play'); - if (player.paused()) { - player.play(); - item.label = 'Pause'; - } else { - player.pause(); - item.label = 'Play'; - } - }}, { - id: "mute", - label: 'Mute', - listener: function () { - var item = getItem('mute'); - if (player.muted()){ - player.muted(false); - item.label = 'Mute'; - } else { - player.muted(true); - item.label = 'Unmute'; - } - }}, { - id: "fullscreen", - label: 'Fill browser', - listener: function () { - var item = getItem('fullscreen'); - if (player.isFullscreen()){ - player.exitFullscreen(); - item.label = 'Fill browser'; - } else { - player.requestFullscreen(); - item.label = 'Unfill browser'; - } - }}, { - // Nested submenu creation is delegated to the player - id: "speed", - label: 'Speed' + // Create nested submenu + if (menuItemClicked && noSubmenuClicked && menuItemsLabelsEqual) { + target.appendChild(docfrag); + } } - ]; - // Fire up vjs-contextmenu-ui plugin - player.contextmenuUI({content: content}); + // Delegate creation of a nested submenu for a context menu + videoPlayer.addEventListener('mouseover', createNestedContextSubMenu); + + // Create context menu options + var content = [ // eslint-disable-line vars-on-top + { + id: 'play', + label: 'Play', + listener: function() { + var item = getItem('play'); // eslint-disable-line no-use-before-define + if (player.paused()) { + player.play(); + item.label = 'Pause'; + } else { + player.pause(); + item.label = 'Play'; + } + } + }, { + id: 'mute', + label: 'Mute', + listener: function() { + var item = getItem('mute'); // eslint-disable-line no-use-before-define + if (player.muted()) { + player.muted(false); + item.label = 'Mute'; + } else { + player.muted(true); + item.label = 'Unmute'; + } + } + }, { + id: 'fullscreen', + label: 'Fill browser', + listener: function() { + var item = getItem('fullscreen'); // eslint-disable-line no-use-before-define + if (player.isFullscreen()) { + player.exitFullscreen(); + item.label = 'Fill browser'; + } else { + player.requestFullscreen(); + item.label = 'Unfill browser'; + } + } + }, { + // Nested submenu creation is delegated to the player + id: 'speed', + label: 'Speed' + } + ]; - // Update context menu labels - var getItem = (function(contextmenuUI) { - var hash = {}; - contextmenuUI.content.forEach(function(item) { - hash[item.id] = item; - }); - return function(id) { - return hash[id]; - }; - }(player.contextmenuUI)); + // Fire up vjs-contextmenu-ui plugin + player.contextmenuUI({content: content}); + // Update context menu labels + var getItem = (function(contextmenuUI) { // eslint-disable-line vars-on-top + var hash = {}; + contextmenuUI.content.forEach(function(item) { + hash[item.id] = item; + }); + return function(id) { + return hash[id]; + }; + }(player.contextmenuUI)); }); diff --git a/video_xblock/static/js/player_state.js b/video_xblock/static/js/player_state.js index 5bc335d8..ee0061e4 100644 --- a/video_xblock/static/js/player_state.js +++ b/video_xblock/static/js/player_state.js @@ -12,51 +12,49 @@ /** Run a callback when DOM is fully loaded */ var domReady = function(callback) { - if (document.readyState === "interactive" || document.readyState === "complete") { - callback(); - } else { - document.addEventListener("DOMContentLoaded", callback); - } + if (document.readyState === "interactive" || document.readyState === "complete") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } }; - var player_state = { - 'volume': {{ player_state.volume }}, - 'currentTime': {{ player_state.current_time }}, - 'playbackRate': {{ player_state.playback_rate }}, - 'muted': {{ player_state.muted | yesno:"true,false" }}, - 'transcriptsEnabled': {{ player_state.transcripts_enabled | yesno:"true,false" }}, - 'captionsEnabled': {{ player_state.captions_enabled | yesno:"true,false" }}, - 'captionsLanguage': '{{ player_state.captions_language }}', + volume: {{ player_state.volume }}, + currentTime: {{ player_state.current_time }}, + playbackRate: {{ player_state.playback_rate }}, + muted: {{ player_state.muted | yesno:'true,false' }}, + transcriptsEnabled: {{ player_state.transcripts_enabled | yesno:'true,false' }}, + captionsEnabled: {{ player_state.captions_enabled | yesno:'true,false' }}, + captionsLanguage: '{{ player_state.captions_language }}' }; - var xblockUsageId = window.location.hash.slice(1); -var transcripts = { - {% for transcript in player_state.transcripts %} - '{{transcript.lang}}': { - 'label': '{{transcript.label}}', - 'url': '{{transcript.url}}', - }, - {% endfor %} -}; +var transcripts = { // eslint-disable-line + {% for transcript in player_state.transcripts %} // eslint-disable-line + '{{transcript.lang}}': { // eslint-disable-line + 'label': '{{transcript.label}}', // eslint-disable-line + 'url': '{{transcript.url}}', // eslint-disable-line + }, // eslint-disable-line + {% endfor %} // eslint-disable-line +}; // eslint-disable-line /** Get transcript url for current caption language */ -var getDownloadTranscriptUrl = function (player){ - var downloadTranscriptUrl; - if (transcripts[player.captionsLanguage]){ - downloadTranscriptUrl = transcripts[player.captionsLanguage].url; - } else { - downloadTranscriptUrl = '#'; - }; - return downloadTranscriptUrl; +var getDownloadTranscriptUrl = function(player) { + var downloadTranscriptUrl; + if (transcripts[player.captionsLanguage]) { + downloadTranscriptUrl = transcripts[player.captionsLanguage].url; + } else { + downloadTranscriptUrl = '#'; + }; + return downloadTranscriptUrl; } /** Restore default or previously saved player state */ -var setInitialState = function (player, state) { +var setInitialState = function(player, state) { var stateCurrentTime = state.currentTime; var playbackProgress = localStorage.getItem('playbackProgress'); if (playbackProgress){ playbackProgress=JSON.parse(playbackProgress); - if (playbackProgress['{{ video_player_id }}'] && + if (playbackProgress['{{ video_player_id }}'] && playbackProgress['{{ video_player_id }}'] > stateCurrentTime) { stateCurrentTime = playbackProgress['{{ video_player_id }}']; } @@ -72,8 +70,8 @@ var setInitialState = function (player, state) { player.captionsEnabled = state.captionsEnabled; player.captionsLanguage = state.captionsLanguage; // To switch off transcripts and captions state if doesn`t have transcripts with current captions language - if (!transcripts[player.captionsLanguage]){ - player.captionsEnabled = player.transcriptsEnabled = false; + if (!transcripts[player.captionsLanguage]) { + player.captionsEnabled = player.transcriptsEnabled = false; }; }; @@ -81,63 +79,60 @@ var setInitialState = function (player, state) { * Save player state by posting it in a message to parent frame. * Parent frame passes it to a server by calling VideoXBlock.save_state() handler. */ -var saveState = function(){ - var player = this; - var new_state = { - 'volume': player.volume(), - 'currentTime': player.ended()? 0 : Math.floor(player.currentTime()), - 'playbackRate': player.playbackRate(), - 'muted': player.muted(), - 'transcriptsEnabled': player.transcriptsEnabled, - 'captionsEnabled': player.captionsEnabled, - 'captionsLanguage': player.captionsLanguage - }; - if (JSON.stringify(new_state) !== JSON.stringify(player_state)) { - console.log('Starting saving player state'); - player_state = new_state; - parent.postMessage({ - 'action': 'saveState', - 'info': new_state, - 'xblockUsageId': xblockUsageId, - 'downloadTranscriptUrl': getDownloadTranscriptUrl(player) - }, - document.location.protocol + "//" + document.location.host - ); - } +var saveState = function() { + var player = this; + var new_state = { + volume: player.volume(), + currentTime: player.ended()? 0 : Math.floor(player.currentTime()), + playbackRate: player.playbackRate(), + muted: player.muted(), + transcriptsEnabled: player.transcriptsEnabled, + captionsEnabled: player.captionsEnabled, + captionsLanguage: player.captionsLanguage + }; + if (JSON.stringify(new_state) !== JSON.stringify(player_state)) { + console.log('Starting saving player state'); + player_state = new_state; + parent.postMessage({ + action: 'saveState', + info: new_state, + xblockUsageId: xblockUsageId, + downloadTranscriptUrl: getDownloadTranscriptUrl(player) + }, + document.location.protocol + '//' + document.location.host + ); + } }; /** * Save player progress in browser's local storage. * We need it when user is switching between tabs. */ -var saveProgressToLocalStore = function(){ - var player = this; - var playbackProgress = localStorage.getItem('playbackProgress'); - if(playbackProgress == undefined){ - playbackProgress = '{}'; - } - playbackProgress = JSON.parse(playbackProgress); - playbackProgress['{{ video_player_id }}'] = player.ended() ? 0 : Math.floor(player.currentTime()); - localStorage.setItem('playbackProgress',JSON.stringify(playbackProgress)); +var saveProgressToLocalStore = function() { + var player = this; + var playbackProgress = localStorage.getItem('playbackProgress'); + if (playbackProgress === undefined) { + playbackProgress = '{}'; + } + playbackProgress = JSON.parse(playbackProgress); + playbackProgress['{{ video_player_id }}'] = player.ended() ? 0 : Math.floor(player.currentTime()); + localStorage.setItem('playbackProgress',JSON.stringify(playbackProgress)); }; domReady(function() { - videojs('{{ video_player_id }}').ready(function() { + videojs('{{ video_player_id }}').ready(function() { var player = this; - // Restore default or previously saved player state setInitialState(player, player_state); - player - .on('timeupdate', saveProgressToLocalStore) - .on('volumechange', saveState) - .on('ratechange', saveState) - .on('play', saveState) - .on('pause', saveState) - .on('ended', saveState) - .on('transcriptstatechanged', saveState) - .on('captionstatechanged', saveState) - .on('currentlanguagechanged', saveState); - }); - + .on('timeupdate', saveProgressToLocalStore) + .on('volumechange', saveState) + .on('ratechange', saveState) + .on('play', saveState) + .on('pause', saveState) + .on('ended', saveState) + .on('transcriptstatechanged', saveState) + .on('captionstatechanged', saveState) + .on('currentlanguagechanged', saveState); + }); }); diff --git a/video_xblock/static/js/studio-edit.js b/video_xblock/static/js/studio-edit.js index 07094aea..7211ddd8 100644 --- a/video_xblock/static/js/studio-edit.js +++ b/video_xblock/static/js/studio-edit.js @@ -1,8 +1,16 @@ +/** + StudioEditableXBlock function for setting up the Video xblock. + This function was copied from xblock-utils by link + https://github.com/edx/xblock-utils/blob/master/xblockutils/templates/studio_edit.html + and extended by Raccoon Gang company + It is responsible for a validating and sending data to backend +*/ function StudioEditableXBlock(runtime, element) { - "use strict"; + 'use strict'; var fields = []; - var tinyMceAvailable = (typeof $.fn.tinymce !== 'undefined'); // Studio includes a copy of tinyMCE and its jQuery plugin + // Studio includes a copy of tinyMCE and its jQuery plugin + var tinyMceAvailable = (typeof $.fn.tinymce !== 'undefined'); var datepickerAvailable = (typeof $.fn.datepicker !== 'undefined'); // Studio includes datepicker jQuery plugin $(element).find('.field-data-control').each(function() { @@ -18,24 +26,31 @@ function StudioEditableXBlock(runtime, element) { val: function() { var val = $field.val(); // Cast values to the appropriate type so that we send nice clean JSON over the wire: - if (type == 'boolean') - return (val == 'true' || val == '1'); - if (type == "integer") + if (type == 'boolean') { // eslint-disable-line + return (val == 'true' || val == '1'); // eslint-disable-line + } + if (type == 'integer') { // eslint-disable-line return parseInt(val, 10); - if (type == "float") + } + if (type == 'float') { // eslint-disable-line return parseFloat(val); - if (type == "generic" || type == "list" || type == "set") { + } + if (type == 'generic' || type == 'list' || type == 'set') { // eslint-disable-line val = val.trim(); - if (val === "") + if (val === '') { val = null; - else + } else { val = JSON.parse(val); // TODO: handle parse errors + } return val; } + /* eslint-disable */ if (type == 'string' && ( - contextId == 'xb-field-edit-start_time' || contextId == 'xb-field-edit-end_time')) { + contextId == 'xb-field-edit-start_time' + || contextId == 'xb-field-edit-end_time')) { return parseRelativeTime(val); } + /* eslint-disable */ return val; }, removeEditor: function() { @@ -47,27 +62,27 @@ function StudioEditableXBlock(runtime, element) { $wrapper.addClass('is-set'); $resetButton.removeClass('inactive').addClass('active'); }; - $field.bind("change input paste", fieldChanged); + $field.bind('change input paste', fieldChanged); $resetButton.click(function() { $field.val($wrapper.attr('data-default')); // Use attr instead of data to force treating the default value as a string $wrapper.removeClass('is-set'); $resetButton.removeClass('active').addClass('inactive'); }); if (type == 'html' && tinyMceAvailable) { - tinyMCE.baseURL = baseUrl + "/js/vendor/tinymce/js/tinymce"; + tinyMCE.baseURL = baseUrl + '/js/vendor/tinymce/js/tinymce'; $field.tinymce({ theme: 'modern', skin: 'studio-tmce4', height: '200px', formats: { code: { inline: 'code' } }, - codemirror: { path: "" + baseUrl + "/js/vendor" }, + codemirror: { path: "" + baseUrl + '/js/vendor' }, convert_urls: false, - plugins: "link codemirror", + plugins: 'link codemirror', menubar: false, statusbar: false, toolbar_items_size: 'small', - toolbar: "formatselect | styleselect | bold italic underline forecolor wrapAsCode | bullist numlist outdent indent blockquote | link unlink | code", - resize: "both", + toolbar: 'formatselect | styleselect | bold italic underline forecolor wrapAsCode | bullist numlist outdent indent blockquote | link unlink | code', + resize: 'both', setup : function(ed) { ed.on('change', fieldChanged); } @@ -76,7 +91,7 @@ function StudioEditableXBlock(runtime, element) { if (type == 'datepicker' && datepickerAvailable) { $field.datepicker('destroy'); - $field.datepicker({dateFormat: "m/d/yy"}); + $field.datepicker({dateFormat: 'm/d/yy'}); } }); @@ -85,6 +100,11 @@ function StudioEditableXBlock(runtime, element) { var $checkboxes = $(this).find('input'); var $wrapper = $optionList.closest('li'); var $resetButton = $wrapper.find('button.setting-clear'); + var fieldChanged = function() { + // Field value has been modified: + $wrapper.addClass('is-set'); + $resetButton.removeClass('inactive').addClass('active'); + }; fields.push({ name: $wrapper.data('field-name'), @@ -100,12 +120,7 @@ function StudioEditableXBlock(runtime, element) { return val; } }); - var fieldChanged = function() { - // Field value has been modified: - $wrapper.addClass('is-set'); - $resetButton.removeClass('inactive').addClass('active'); - }; - $checkboxes.bind("change input", fieldChanged); + $checkboxes.bind('change input', fieldChanged); $resetButton.click(function() { var defaults = JSON.parse($wrapper.attr('data-default')); @@ -120,26 +135,26 @@ function StudioEditableXBlock(runtime, element) { var studio_submit = function(data) { var handlerUrl = runtime.handlerUrl(element, 'submit_studio_edits'); - runtime.notify('save', {state: 'start', message: gettext("Saving")}); + runtime.notify('save', {state: 'start', message: gettext('Saving')}); $.ajax({ - type: "POST", + type: 'POST', url: handlerUrl, data: JSON.stringify(data), - dataType: "json", + dataType: 'json', global: false, // Disable Studio's error handling that conflicts with studio's notify('save') and notify('cancel') :-/ success: function(response) { runtime.notify('save', {state: 'end'}); } }).fail(function(jqXHR) { - var message = gettext("This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online."); + var message = gettext('This may be happening because of an error with our server or your internet connection. Try refreshing the page or making sure you are online.'); if (jqXHR.responseText) { // Is there a more specific error message we can show? try { message = JSON.parse(jqXHR.responseText).error; - if (typeof message === "object" && message.messages) { + if (typeof message === 'object' && message.messages) { // e.g. {"error": {"messages": [{"text": "Unknown user 'bob'!", "type": "error"}, ...]}} etc. message = $.map(message.messages, function(msg) { return msg.text; }).join(", "); } } catch (error) { message = jqXHR.responseText.substr(0, 300); } } - runtime.notify('error', {title: gettext("Unable to update settings"), message: message}); + runtime.notify('error', {title: gettext('Unable to update settings'), message: message}); }); }; @@ -164,7 +179,7 @@ function StudioEditableXBlock(runtime, element) { studio_submit({values: values, defaults: notSet}); }; - var validateTranscripts = function(e){ + var validateTranscripts = function(e) { e.preventDefault(); var isValid = []; var $visibleLangChoiceItems = $langChoiceItem.find('li:visible'); @@ -177,7 +192,7 @@ function StudioEditableXBlock(runtime, element) { isValid.push(1) } }); - if (isValid.length == $visibleLangChoiceItems.length){ + if (isValid.length == $visibleLangChoiceItems.length) { fillValues(e) } }; @@ -227,15 +242,15 @@ function StudioEditableXBlock(runtime, element) { }) }; /** Replaces an existing transcript to transcriptsValue or adds new */ - var pushTranscript = function (lang, label, url, oldLang, $uploadButton){ + var pushTranscript = function (lang, label, url, oldLang, $uploadButton) { var indexLanguage; - for (var i=0; i < transcriptsValue.length; i++){ - if (oldLang == transcriptsValue[i].lang || lang == transcriptsValue[i].lang){ + for (var i=0; i < transcriptsValue.length; i++) { + if (oldLang == transcriptsValue[i].lang || lang == transcriptsValue[i].lang) { indexLanguage = i; break; } } - if (indexLanguage !== undefined){ + if (indexLanguage !== undefined) { transcriptsValue[indexLanguage].lang = lang; transcriptsValue[indexLanguage].label = label; if (url) { @@ -254,7 +269,7 @@ function StudioEditableXBlock(runtime, element) { $('.add-transcript').removeClass('is-disabled'); }; - var clickUploader = function(event){ + var clickUploader = function(event) { event.preventDefault(); event.stopPropagation(); var $buttonBlock = $(event.currentTarget); @@ -278,10 +293,10 @@ function StudioEditableXBlock(runtime, element) { var $langSelectParent = $(event.currentTarget).parent('li'); var $uploadButton = $('.upload-transcript', $langSelectParent); var oldLang = $uploadButton.data('lang-code'); - if (selectedLanguage != oldLang && selectedLanguage != ''){ + if (selectedLanguage != oldLang && selectedLanguage != '') { pushTranscript(selectedLanguage, languageLabel, undefined, oldLang, $uploadButton); disabledLanguages.push(selectedLanguage); - if (oldLang != ''){ + if (oldLang != '') { removeLanguage(oldLang); } $uploadButton.data('lang-code', selectedLanguage); @@ -297,36 +312,36 @@ function StudioEditableXBlock(runtime, element) { pushTranscriptsValue(); }; - var removeLanguage = function(language){ + var removeLanguage = function(language) { var index = disabledLanguages.indexOf(language); disabledLanguages.splice(index, 1); }; - var removeTranscript = function(lang){ - for (var i=0; i < transcriptsValue.length; i++){ - if (lang == transcriptsValue[i].lang){ + var removeTranscript = function(lang) { + for (var i=0; i < transcriptsValue.length; i++) { + if (lang == transcriptsValue[i].lang) { transcriptsValue.splice(i,1); break; } } }; - var pushTranscriptsValue = function(){ - transcriptsValue.forEach(function (transcriptValue, index, array){ - if (transcriptValue.lang == "" || transcriptValue.label == "" || transcriptValue.url == ""){ + var pushTranscriptsValue = function() { + transcriptsValue.forEach(function (transcriptValue, index, array) { + if (transcriptValue.lang == '' || transcriptValue.label == '' || transcriptValue.url == '') { transcriptsValue.splice(index, 1); } }); $('input[data-field-name="transcripts"]').val(JSON.stringify(transcriptsValue)).change(); }; - var removeTranscriptBlock = function(event){ + var removeTranscriptBlock = function(event) { event.preventDefault(); event.stopPropagation(); var $currentBlock = $(event.currentTarget).closest('li'); var lang = $currentBlock.find('option:selected').val(); removeTranscript(lang); - if (!transcriptsValue.length){ + if (!transcriptsValue.length) { $currentBlock.parents('li').removeClass('is-set').find('.setting-clear').removeClass('active').addClass('inactive'); } removeLanguage(lang); @@ -337,11 +352,11 @@ function StudioEditableXBlock(runtime, element) { }; - var showUploadStatus = function($element, filename){ + var showUploadStatus = function($element, filename) { $('.status-error', $element).empty(); $('.status-upload', $element).text('File ' + '"' + filename + '"' + ' uploaded successfully').show(); - setTimeout(function(){ - $('.status-upload', $element).hide() + setTimeout(function() { + $('.status-upload', $element).hide(); }, 5000); }; @@ -350,7 +365,7 @@ function StudioEditableXBlock(runtime, element) { var regExp = /.*@(.+\..+)/; var filename = regExp.exec(url)[1]; var downloadUrl = downloadTranscriptHandlerUrl + '?' + url; - if (fieldName == "handout"){ + if (fieldName == 'handout') { var $parentDiv = $('.file-uploader', element); $('.download-setting', $parentDiv).attr('href', downloadUrl).removeClass('is-hidden'); $('a[data-change-field-name=' + fieldName + ']').text('Replace'); @@ -373,7 +388,7 @@ function StudioEditableXBlock(runtime, element) { }; $fileUploader.on('change', function(event) { - if (!$fileUploader.val()){ + if (!$fileUploader.val()) { return; }; var fieldName = $(event.currentTarget).attr('data-change-field-name'); @@ -383,20 +398,20 @@ function StudioEditableXBlock(runtime, element) { var currentLiTag = $('.language-transcript-selector').children()[parseInt(currentLiIndex)]; $('.upload-setting', element).addClass('is-disabled'); $('.file-uploader-form', element).ajaxSubmit({ - success: function(response, statusText, xhr){ + success: function(response, statusText, xhr) { successHandler(response, statusText, xhr, fieldName, lang, label, currentLiTag) }, - error: function(jqXHR, textStatus, errorThrown){ - runtime.notify('error', {title: gettext("Unable to update settings"), message: textStatus}); + error: function(jqXHR, textStatus, errorThrown) { + runtime.notify('error', {title: gettext('Unable to update settings'), message: textStatus}); } }); $('.upload-setting', element).removeClass('is-disabled'); }); $('.add-transcript', element).on('click', function (event) { + var $templateItem = $('.list-settings-item:hidden').clone(); event.preventDefault(); $(event.currentTarget).addClass('is-disabled'); - var $templateItem = $('.list-settings-item:hidden').clone(); $templateItem.removeClass('is-hidden').appendTo($langChoiceItem); $('.upload-transcript', $templateItem).on('click', clickUploader); $('.lang-select', $templateItem).on('change', languageChecker); @@ -409,15 +424,15 @@ function StudioEditableXBlock(runtime, element) { $('.remove-action', element).on('click', removeTranscriptBlock); - $('.setting-clear').on('click', function (event){ + $('.setting-clear').on('click', function (event) { var $currentBlock = $(event.currentTarget).closest('li'); - if ($('.file-uploader', $currentBlock).length > 0){ + if ($('.file-uploader', $currentBlock).length > 0) { $('.upload-setting', $currentBlock).text('Upload'); $('.download-setting', $currentBlock).addClass('is-hidden'); } $currentBlock.find('ol').find('li:visible').remove(); }); - $().ready(function(){ + $().ready(function() { disableOption(); }); @@ -427,7 +442,7 @@ function StudioEditableXBlock(runtime, element) { // that is 86399 seconds. var maxTimeInSeconds = 86399; var pad = function (number) { - return (number < 10) ? "0" + number : number; + return (number < 10) ? '0' + number : number; }; // Removes all white-spaces and splits by `:`. var list = value.replace(/\s+/g, '').split(':'); diff --git a/video_xblock/static/js/toggle-button.js b/video_xblock/static/js/toggle-button.js index d2c02bfb..970a899c 100644 --- a/video_xblock/static/js/toggle-button.js +++ b/video_xblock/static/js/toggle-button.js @@ -4,147 +4,152 @@ */ domReady(function() { - - var MenuItem = videojs.getComponent('MenuItem'); - var ToggleMenuItem = videojs.extend(MenuItem, { - // base class for create buttons for caption and transcripts - constructor: function constructor(player, options) { - MenuItem.call(this, player, options); - this.on('click', this.onClick); - this.createEl(); - }, - enabledEventName: function enabledEventName() { - return this.options_['enabledEvent']; - }, - disabledEventName: function disabledEventName() { - return this.options_['disabledEvent']; - }, - createEl: function createEl(type, props, attributes) { - props = props || {}; - var el = MenuItem.prototype.createEl.call(this, arguments.tag, props, attributes); - el.setAttribute('role', 'menuitem'); - el.setAttribute('aria-live', 'polite'); - el.setAttribute('data-lang', this.options_.track.language); - return el; - }, - onClick: function onClick(event) { - var menuItem = this.$$('.vjs-menu-item', this.el_.parentNode); - var el = event.currentTarget; - Array.from(menuItem).forEach(function(caption) { - caption.classList.remove('vjs-selected'); - }); - el.classList.add('vjs-selected'); - - var tracks = this.player_.textTracks(); - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - if (track.kind === 'captions') { - this.player_.captionsLanguage = el.dataset.lang; - track.mode = track.language === this.player_.captionsLanguage ? 'showing': 'disabled'; + 'use strict'; + + var MenuItem = videojs.getComponent('MenuItem'); + var ToggleMenuItem = videojs.extend(MenuItem, { + // base class for create buttons for caption and transcripts + constructor: function constructor(player, options) { + MenuItem.call(this, player, options); + this.on('click', this.onClick); + this.createEl(); + }, + enabledEventName: function enabledEventName() { + return this.options_.enabledEvent; + }, + disabledEventName: function disabledEventName() { + return this.options_.disabledEvent; + }, + createEl: function createEl(type, props, attributes) { + var el = MenuItem.prototype.createEl.call(this, arguments.tag, props, attributes); + props = props || {}; // eslint-disable-line no-param-reassign + el.setAttribute('role', 'menuitem'); + el.setAttribute('aria-live', 'polite'); + el.setAttribute('data-lang', this.options_.track.language); + return el; + }, + onClick: function onClick(event) { + var menuItem = this.$$('.vjs-menu-item', this.el_.parentNode); + var el = event.currentTarget; + var tracks = this.player_.textTracks(); + Array.from(menuItem).forEach(function(caption) { + caption.classList.remove('vjs-selected'); + }); + el.classList.add('vjs-selected'); + + for (var i = 0; i < tracks.length; i++) { // eslint-disable-line vars-on-top + var track = tracks[i]; // eslint-disable-line vars-on-top + if (track.kind === 'captions') { + this.player_.captionsLanguage = el.dataset.lang; + if (track.language === this.player_.captionsLanguage) { + track.mode = 'showing'; + } else { + track.mode = 'disabled'; + } + } + } + this.player_.trigger('captionstrackchange'); + this.player_.trigger('subtitlestrackchange'); + this.player_.trigger('currentlanguagechanged'); } - } - this.player_.trigger('captionstrackchange'); - this.player_.trigger('subtitlestrackchange'); - this.player_.trigger('currentlanguagechanged'); - } - }); - - var MenuButton = videojs.getComponent('MenuButton'); - var ClickableComponent = videojs.getComponent('ClickableComponent'); - - var ToggleButton = videojs.extend(MenuButton, { - // base class for create buttons for caption and transcripts - constructor: function constructor(player, options) { - this.kind_ = 'captions'; - - ClickableComponent.call(this, player, options); - - if (!this.player_.singleton_menu) { - this.update(); - this.player_.singleton_menu = this.menu; - } else { - this.menu = this.player_.singleton_menu; - } - - this.enabled_ = true; - this.el_.setAttribute('aria-haspopup', 'true'); - this.el_.setAttribute('role', 'menuitem'); - - this.on('click', this.onClick); - this.on('mouseenter', function(event) { - this.menu.el_.classList.add('is-visible'); - }); - this.on('mouseout', function(event) { - this.menu.el_.classList.remove('is-visible'); - }); - - this.createEl(); - }, - - createItems: function createItems() { - var items = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; - var tracks = this.player_.textTracks(); - - if (!tracks) { - return items; - } - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - // only add tracks that are of the appropriate kind and have a label - if (track['kind'] === this.kind_) { - items.push(new ToggleMenuItem(this.player_, { - // MenuItem is selectable - 'track': track, - 'label': track.label, - 'enabledEvent': this.enabledEventName(), - 'disabledEvent': this.disabledEventName() - })); + }); + + var MenuButton = videojs.getComponent('MenuButton'); + var ClickableComponent = videojs.getComponent('ClickableComponent'); + + var ToggleButton = videojs.extend(MenuButton, { + // base class for create buttons for caption and transcripts + constructor: function constructor(player, options) { + this.kind_ = 'captions'; + + ClickableComponent.call(this, player, options); + + if (!this.player_.singleton_menu) { + this.update(); + this.player_.singleton_menu = this.menu; + } else { + this.menu = this.player_.singleton_menu; + } + // This variable uses in videojs library + this.enabled_ = true; // eslint-disable-line no-underscore-dangle + this.el_.setAttribute('aria-haspopup', 'true'); + this.el_.setAttribute('role', 'menuitem'); + + this.on('click', this.onClick); + this.on('mouseenter', function() { + this.menu.el_.classList.add('is-visible'); + }); + this.on('mouseout', function() { + this.menu.el_.classList.remove('is-visible'); + }); + + this.createEl(); + }, + + createItems: function createItems() { + var items = arguments.length <= 0 || arguments[0] === undefined ? [] : arguments[0]; + var tracks = this.player_.textTracks(); + + if (!tracks) { + return items; + } + for (var i = 0; i < tracks.length; i++) { // eslint-disable-line vars-on-top + var track = tracks[i]; // eslint-disable-line vars-on-top + // only add tracks that are of the appropriate kind and have a label + if (track.kind === this.kind_) { + items.push(new ToggleMenuItem(this.player_, { + // MenuItem is selectable + track: track, + label: track.label, + enabledEvent: this.enabledEventName(), + disabledEvent: this.disabledEventName() + })); + } + } + return items; + }, + + styledSpan: function styledSpan() { + return this.options_.style; + }, + enabledEventName: function enabledEventName() { + return this.options_.enabledEvent; + }, + disabledEventName: function disabledEventName() { + return this.options_.disabledEvent; + }, + buildCSSClass: function buildCSSClass() { + return this.options_.cssClasses; + }, + createEl: function createEl(props, attributes) { + var el = MenuButton.prototype.createEl.call(this, arguments.tag, props, attributes); + props = props || {}; // eslint-disable-line no-param-reassign + props.className = this.buildCSSClass() + // eslint-disable-line no-param-reassign + ' icon fa ' + this.styledSpan(); + props.tabIndex = 0; // eslint-disable-line no-param-reassign + el.setAttribute('role', 'menuitem'); + el.setAttribute('aria-live', 'polite'); + el.classList += ' icon fa ' + this.styledSpan(); + el.classList.add('vjs-singleton'); + + return el; + }, + onClick: function onClick(event) { + var el = event.currentTarget; + var eventName = this.hasClass('vjs-control-enabled') ? this.enabledEventName() : this.disabledEventName(); + el.classList.toggle('vjs-control-enabled'); + this.player_.trigger(eventName); } - } - return items; - }, - - styledSpan: function styledSpan() { - return this.options_['style']; - }, - enabledEventName: function enabledEventName() { - return this.options_['enabledEvent']; - }, - disabledEventName: function disabledEventName() { - return this.options_['disabledEvent']; - }, - buildCSSClass: function buildCSSClass() { - return this.options_['cssClasses']; - }, - createEl: function createEl(props, attributes) { - props = props || {}; - props['className'] = this.buildCSSClass() + ' icon fa ' + this.styledSpan(); - props['tabIndex'] = 0; - var el = MenuButton.prototype.createEl.call(this, arguments.tag, props, attributes); - el.setAttribute('role', 'menuitem'); - el.setAttribute('aria-live', 'polite'); - el.classList += ' icon fa ' + this.styledSpan(); - el.classList.add('vjs-singleton'); + }); - return el; - }, - onClick: function onClick(event) { - var el = event.currentTarget; - el.classList.toggle('vjs-control-enabled'); - var eventName = this.hasClass('vjs-control-enabled') ? this.enabledEventName() : this.disabledEventName(); - this.player_.trigger(eventName); - }, - - }); - - var toggleButton = function(options){ - if (this.tagAttributes.brightcove !== undefined) { - this.controlBar.customControlSpacer.addChild('ToggleButton', options); - } else { - this.controlBar.addChild('ToggleButton', options); - } - }; + var toggleButton = function(options) { + if (this.tagAttributes.brightcove !== undefined) { + this.controlBar.customControlSpacer.addChild('ToggleButton', options); + } else { + this.controlBar.addChild('ToggleButton', options); + } + }; - videojs.registerComponent('ToggleButton', ToggleButton); - videojs.plugin('toggleButton', toggleButton); + videojs.registerComponent('ToggleButton', ToggleButton); + videojs.plugin('toggleButton', toggleButton); }); diff --git a/video_xblock/static/js/video_xblock.js b/video_xblock/static/js/video_xblock.js index 2a937fa1..27abe491 100644 --- a/video_xblock/static/js/video_xblock.js +++ b/video_xblock/static/js/video_xblock.js @@ -1,69 +1,76 @@ /** Javascript for VideoXBlock.student_view() */ function VideoXBlockStudentViewInit(runtime, element) { - var stateHandlerUrl = runtime.handlerUrl(element, 'save_player_state'); - var eventHandlerUrl = runtime.handlerUrl(element, 'publish_event'); - var downloadTranscriptHandlerUrl = runtime.handlerUrl(element, 'download_transcript'); - var usageId = element.attributes['data-usage-id'].value; + 'use strict'; + var stateHandlerUrl = runtime.handlerUrl(element, 'save_player_state'); + var eventHandlerUrl = runtime.handlerUrl(element, 'publish_event'); + var downloadTranscriptHandlerUrl = runtime.handlerUrl(element, 'download_transcript'); + var usageId = element.attributes['data-usage-id'].value; + window.videoXBlockState = window.videoXBlockState || {}; + var handlers = window.videoXBlockState.handlers = // eslint-disable-line vars-on-top + window.videoXBlockState.handlers || { + saveState: {}, + analytics: {} + }; - window.videoXBlockState = window.videoXBlockState || {}; - var handlers = window.videoXBlockState.handlers = window.videoXBlockState.handlers || { - saveState: {}, - analytics: {} - }; - handlers.saveState[usageId] = stateHandlerUrl; - handlers.analytics[usageId] = eventHandlerUrl; + handlers.saveState[usageId] = stateHandlerUrl; + handlers.analytics[usageId] = eventHandlerUrl; - /** Send data to server by POSTing it to appropriate VideoXBlock handler */ - function sendData(handlerUrl, data) { - $.ajax({ - type: "POST", - url: handlerUrl, - data: JSON.stringify(data) - }) - .done(function() { - console.log('Data processed successfully.'); - }) - .fail(function() { - console.log('Failed to process data'); - }); - } - - if (!window.videoXBlockListenerRegistered) { - // Make sure we register event listener only once even if there are more than - // one VideoXBlock on a page - window.addEventListener("message", receiveMessage, false); - window.videoXBlockListenerRegistered = true; - } + /** Send data to server by POSTing it to appropriate VideoXBlock handler */ + function sendData(handlerUrl, data) { + $.ajax({ + type: 'POST', + url: handlerUrl, + data: JSON.stringify(data) + }) + .done(function() { + console.log('Data processed successfully.'); // eslint-disable-line no-console + }) + .fail(function() { + console.log('Failed to process data'); // eslint-disable-line no-console + }); + } - /** - * Receive a message from child frames. - * Expects a specific type of messages containing video player state to be saved on a server. - * Pass the sate to `saveState()` for handling. - */ - function receiveMessage(event) { - var origin = event.origin || event.originalEvent.origin; // For Chrome, the origin property is in the event.originalEvent object. - if (origin !== document.location.protocol + "//" + document.location.host) - // Discard a message received from another domain - return; - try { - if (event.data.action === "saveState") { - updateTranscriptDownloadUrl(event.data.downloadTranscriptUrl); - }; + if (!window.videoXBlockListenerRegistered) { + // Make sure we register event listener only once even if there are more than + // one VideoXBlock on a page + window.addEventListener('message', receiveMessage, false); // eslint-disable-line no-use-before-define + window.videoXBlockListenerRegistered = true; + } - var url = handlers[event.data.action][event.data.xblockUsageId]; - if (url) { - sendData(url, event.data.info); - } - } catch (err){ - console.log(err) + /** + * Receive a message from child frames. + * Expects a specific type of messages containing video player state to be saved on a server. + * Pass the sate to `saveState()` for handling. + */ + function receiveMessage(event) { + // For Chrome, the origin property is in the event.originalEvent object. + var origin = event.origin || event.originalEvent.origin; + if (origin !== document.location.protocol + '//' + document.location.host) { + // Discard a message received from another domain + return; + } + try { + var url = handlers[event.data.action][event.data.xblockUsageId]; // eslint-disable-line vars-on-top + if (event.data.action === 'saveState') { + updateTranscriptDownloadUrl( // eslint-disable-line no-use-before-define + event.data.downloadTranscriptUrl + ); + } + if (url) { + sendData(url, event.data.info); + } + } catch (err) { + console.log(err); // eslint-disable-line no-console + } + } + /** Updates transcript download url if it is enabled */ + function updateTranscriptDownloadUrl(downloadTranscriptUrl) { + try { + var downloadLinkEl = document.getElementById('download-transcript-link'); // eslint-disable-line vars-on-top + downloadLinkEl.href = downloadTranscriptHandlerUrl + '?' + downloadTranscriptUrl; + } catch (err) { + console.log(err); // eslint-disable-line no-console + } } - }; - /** Updates transcript download url if it is enabled */ - function updateTranscriptDownloadUrl(downloadTranscriptUrl) { - try { - var downloadLinkEl = document.getElementById('download-transcript-link'); - downloadLinkEl.href = downloadTranscriptHandlerUrl + '?' + downloadTranscriptUrl; - } catch (err){} - } } diff --git a/video_xblock/static/js/videojs-speed-handler.js b/video_xblock/static/js/videojs-speed-handler.js index b0cc4f81..be467ed5 100644 --- a/video_xblock/static/js/videojs-speed-handler.js +++ b/video_xblock/static/js/videojs-speed-handler.js @@ -6,15 +6,14 @@ * */ -(function () { - - "use strict"; +(function() { + 'use strict'; + /** + Videojs speed handler + */ function videoJSSpeedHandler(options) { - var playbackRateMenuButton = videojs.getComponent('PlaybackRateMenuButton'); var controlBar = videojs.getComponent('ControlBar'); - var menuButton = videojs.getComponent('MenuButton'); - var button = videojs.getComponent('Button'); var videojsPlayer = videojs('{{ video_player_id }}'); /** @@ -27,7 +26,7 @@ */ var playbackRateMenuButtonExtended = videojs.extend(playbackRateMenuButton, { /** @constructor */ - constructor: function (player, options) { + constructor: function(player, options) { // eslint-disable-line no-shadow playbackRateMenuButton.call(this, player, options); this.on('ratechange', this.updateLabel); this.on('click', this.handleClick); @@ -40,7 +39,7 @@ * * @method updateLabel */ - playbackRateMenuButtonExtended.prototype.updateLabel = function(event){ + playbackRateMenuButtonExtended.prototype.updateLabel = function() { var speed = this.player().playbackRate() || 1; this.labelEl_.innerHTML = speed + 'x'; }; @@ -51,7 +50,7 @@ * * @method handleClick */ - playbackRateMenuButtonExtended.prototype.handleClick = function(event){ + playbackRateMenuButtonExtended.prototype.handleClick = function() { // FIXME for Brightcove return false; }; @@ -67,12 +66,9 @@ } else { controlBar.prototype.options_.children.push('PlaybackRateMenuButton'); } - return this; } - // Export plugin to the root window.videoJSSpeedHandler = videoJSSpeedHandler; window.videojs.plugin('videoJSSpeedHandler', videoJSSpeedHandler); - }).call(this); diff --git a/video_xblock/static/js/videojs-tabindex.js b/video_xblock/static/js/videojs-tabindex.js index e72301ba..74f78bd7 100644 --- a/video_xblock/static/js/videojs-tabindex.js +++ b/video_xblock/static/js/videojs-tabindex.js @@ -1,55 +1,54 @@ /** * This part is responsible for the order tabindex. - * + * * Determine and call the function orderTabIndex. - * + * * orderTabIndex - is function what rearrange controlBar elements * in right order */ domReady(function() { - videojs('{{ video_player_id }}').ready(function() { + 'use strict'; + videojs('{{ video_player_id }}').ready(function() { + /** Order tabIndex in player control */ + var orderTabIndex = function orderTabIndex(_player) { + var controlBar; + if (_player.tagAttributes.brightcove === undefined) { + controlBar = _player.controlBar.childNameIndex_; + } else { + controlBar = _player.controlBar; + } + /* eslint-disable vars-on-top */ + var controlBarActions = Object.keys(controlBar); + var controlsTabOrder = [ + 'progressControl', + 'playToggle', + 'playbackRateMenuButton', + 'volumeMenuButton', + 'fullscreenToggle', + 'captionsButton' + ]; + var controlsMap = { + progressControl: controlBar.progressControl.seekBar.el_, + playToggle: controlBar.playToggle.el_, + captionsButton: controlBar.captionsButton.el_, + volumeMenuButton: controlBar.volumeMenuButton.volumeBar.el_, + fullscreenToggle: controlBar.fullscreenToggle.el_, + playbackRateMenuButton: controlBar.playbackRateMenuButton.el_ + }; + /* eslint-enable vars-on-top */ - /** Order tabIndex in player control */ - var orderTabIndex = function orderTabIndex(_player) { - var controlBar; - if (_player.tagAttributes.brightcove === undefined){ - controlBar = _player.controlBar.childNameIndex_; - } else { - controlBar = _player.controlBar; - }; - var controlBarActions = Object.keys(controlBar); - var controlsTabOrder = [ - 'progressControl', - 'playToggle', - 'playbackRateMenuButton', - 'volumeMenuButton', - 'fullscreenToggle', - 'captionsButton' - ]; - var controlsMap = { - 'progressControl': controlBar.progressControl.seekBar.el_, - 'playToggle': controlBar.playToggle.el_, - 'captionsButton': controlBar.captionsButton.el_, - 'volumeMenuButton': controlBar.volumeMenuButton.volumeBar.el_, - 'fullscreenToggle': controlBar.fullscreenToggle.el_, - 'playbackRateMenuButton': controlBar.playbackRateMenuButton.el_ - }; - - // Switch off tabIndex for volumeMenuButton and free slot for volumeBar - controlBar.volumeMenuButton.el_.tabIndex = -1; - - controlBarActions.forEach(function(action) { - var el = controlsMap[action] || controlBar[action].el_; - if (el) { - var index = controlsTabOrder.indexOf(action); - el.tabIndex = index === -1 ? -1 : index + 1; - } - }); - }; - - orderTabIndex(this); - - }); + // Switch off tabIndex for volumeMenuButton and free slot for volumeBar + controlBar.volumeMenuButton.el_.tabIndex = -1; + controlBarActions.forEach(function(action) { + var el = controlsMap[action] || controlBar[action].el_; // eslint-disable-line vars-on-top + if (el) { + var index = controlsTabOrder.indexOf(action); // eslint-disable-line vars-on-top + el.tabIndex = index === -1 ? -1 : index + 1; + } + }); + }; + orderTabIndex(this); + }); }); diff --git a/video_xblock/static/js/videojs-transcript.js b/video_xblock/static/js/videojs-transcript.js index d386ec4b..66c58248 100644 --- a/video_xblock/static/js/videojs-transcript.js +++ b/video_xblock/static/js/videojs-transcript.js @@ -1,94 +1,87 @@ domReady(function() { - videojs('{{ video_player_id }}').ready(function() { - - var tracks = this.textTracks(); - var enableTrack = false; - for (var i = 0; i < tracks.length; i++) { - var track = tracks[i]; - if (track.kind === 'captions') { - if (track.language === this.captionsLanguage) { - track.mode = 'showing'; - enableTrack = true; - } else { - track.mode = 'disabled'; + 'use strict'; + videojs('{{ video_player_id }}').ready(function() { + var tracks = this.textTracks(); + var enableTrack = false; + var cssClasses = 'vjs-custom-caption-button vjs-menu-button vjs-menu-button-popup vjs-control vjs-button'; + // fire up the plugin + var transcript = this.transcript({ + showTrackSelector: false, + showTitle: false, + followPlayerTrack: true + }); + // attach the widget to the page + var transcriptContainer = document.getElementById('transcript'); + var captionContainer = document.getElementsByClassName('vjs-text-track-display'); + for (var i = 0; i < tracks.length; i++) { // eslint-disable-line vars-on-top + var track = tracks[i]; // eslint-disable-line vars-on-top + if (track.kind === 'captions') { + if (track.language === this.captionsLanguage) { + track.mode = 'showing'; + enableTrack = true; + } else { + track.mode = 'disabled'; + } + if (!enableTrack && i === tracks.length - 1) { + tracks[0].mode = 'showing'; + } + } } - }; - }; - if (!enableTrack && track.kind === 'captions') { - tracks[0].mode = 'showing'; - } - - // fire up the plugin - var transcript = this.transcript({ - 'showTrackSelector': false, - 'showTitle': false, - 'followPlayerTrack': true - }); - - // attach the widget to the page - var transcriptContainer = document.getElementById('transcript'); - - // Show or hide the transcripts block depending on the transcript state - if (!this.transcriptsEnabled){ - transcriptContainer.className += " is-hidden"; - }; - transcriptContainer.appendChild(transcript.el()); - - this.on('transcriptenabled', function(){ - transcriptContainer.classList.toggle('is-hidden'); - this.transcriptsEnabled = true; - this.trigger('transcriptstatechanged'); - }); - this.on('transcriptdisabled', function(){ - transcriptContainer.classList.toggle('is-hidden'); - this.transcriptsEnabled = false; - this.trigger('transcriptstatechanged'); - }); - - var captionContainer = document.getElementsByClassName('vjs-text-track-display'); - - // Show or hide the captions block depending on the caption state - if (!this.captionsEnabled){ - Array.from(captionContainer).forEach(function(caption) { - caption.className += " is-hidden"; - }); - }; + // Show or hide the transcripts block depending on the transcript state + if (!this.transcriptsEnabled) { + transcriptContainer.className += ' is-hidden'; + } + transcriptContainer.appendChild(transcript.el()); - this.on('captionenabled', function(){ - Array.from(captionContainer).forEach(function(caption) { - caption.classList.toggle('is-hidden', false); - }); - this.captionsEnabled = true; - this.trigger('captionstatechanged'); - }); - this.on('captiondisabled', function(){ - Array.from(captionContainer).forEach(function(caption) { - caption.classList.toggle('is-hidden', true); - }); - this.captionsEnabled = false; - this.trigger('captionstatechanged'); - }); + this.on('transcriptenabled', function() { + transcriptContainer.classList.toggle('is-hidden'); + this.transcriptsEnabled = true; + this.trigger('transcriptstatechanged'); + }); + this.on('transcriptdisabled', function() { + transcriptContainer.classList.toggle('is-hidden'); + this.transcriptsEnabled = false; + this.trigger('transcriptstatechanged'); + }); + // Show or hide the captions block depending on the caption state + if (!this.captionsEnabled) { + Array.from(captionContainer).forEach(function(caption) { + caption.className += ' is-hidden'; // eslint-disable-line no-param-reassign + }); + } - var cssClasses = "vjs-custom-caption-button vjs-menu-button vjs-menu-button-popup vjs-control vjs-button"; - if (this.captionsEnabled){ - cssClasses += ' vjs-control-enabled'; - }; - this.toggleButton({ - style: "fa-cc", - enabledEvent: "captionenabled", - disabledEvent: "captiondisabled", - cssClasses: cssClasses, - }); - cssClasses = "vjs-custom-transcript-button vjs-menu-button vjs-menu-button-popup vjs-control vjs-button"; - if (this.transcriptsEnabled){ - cssClasses += ' vjs-control-enabled'; - }; - this.toggleButton({ - style: "fa-quote-left", - enabledEvent: "transcriptenabled", - disabledEvent: "transcriptdisabled", - cssClasses: cssClasses, + this.on('captionenabled', function() { + Array.from(captionContainer).forEach(function(caption) { + caption.classList.toggle('is-hidden', false); + }); + this.captionsEnabled = true; + this.trigger('captionstatechanged'); + }); + this.on('captiondisabled', function() { + Array.from(captionContainer).forEach(function(caption) { + caption.classList.toggle('is-hidden', true); + }); + this.captionsEnabled = false; + this.trigger('captionstatechanged'); + }); + if (this.captionsEnabled) { + cssClasses += ' vjs-control-enabled'; + } + this.toggleButton({ + style: 'fa-cc', + enabledEvent: 'captionenabled', + disabledEvent: 'captiondisabled', + cssClasses: cssClasses + }); + cssClasses = 'vjs-custom-transcript-button vjs-menu-button vjs-menu-button-popup vjs-control vjs-button'; + if (this.transcriptsEnabled) { + cssClasses += ' vjs-control-enabled'; + } + this.toggleButton({ + style: 'fa-quote-left', + enabledEvent: 'transcriptenabled', + disabledEvent: 'transcriptdisabled', + cssClasses: cssClasses + }); }); - - }); }); diff --git a/video_xblock/static/js/videojs_event_plugin.js b/video_xblock/static/js/videojs_event_plugin.js index 0446260d..5336546a 100644 --- a/video_xblock/static/js/videojs_event_plugin.js +++ b/video_xblock/static/js/videojs_event_plugin.js @@ -9,16 +9,14 @@ * - onSpeedChange * */ -(function () { - - "use strict"; +(function() { + 'use strict'; /** * Videojs plugin. * Listens for events and send them to parent frame to be logged in Open edX tracking log * @param {Object} options - Plugin options passed in at initialization time. */ - function XBlockEventPlugin(options) { - + function XBlockEventPlugin() { var previousTime = 0; var currentTime = 0; @@ -37,36 +35,36 @@ 'onHideCaptions' ]; - this.onReady = function () { + this.onReady = function() { this.log('ready_video'); }; - this.onPlay = function () { + this.onPlay = function() { this.log('play_video', {currentTime: this.currentTime()}); }; - this.onPause = function () { + this.onPause = function() { this.log('pause_video', {currentTime: this.currentTime()}); }; - this.onEnded = function () { + this.onEnded = function() { this.log('stop_video', {currentTime: this.currentTime()}); }; - this.onSkip = function (event, doNotShowAgain) { + this.onSkip = function(event, doNotShowAgain) { var info = {currentTime: this.currentTime()}, eventName = doNotShowAgain ? 'do_not_show_again_video' : 'skip_video'; this.log(eventName, info); }; - this.onSeek = function () { + this.onSeek = function() { this.log('seek_video', { previous_time: previousTime, new_time: currentTime }); }; - this.onSpeedChange = function (event, newSpeed, oldSpeed) { + this.onSpeedChange = function(event, newSpeed, oldSpeed) { this.log('speed_change_video', { current_time: this.currentTime(), old_speed: oldSpeed, @@ -74,80 +72,77 @@ }); }; - this.onShowLanguageMenu = function () { + this.onShowLanguageMenu = function() { this.log('language_menu.shown'); }; - this.onHideLanguageMenu = function () { + this.onHideLanguageMenu = function() { this.log('language_menu.hidden', {language: this.language}); }; - this.onShowTranscript = function () { + this.onShowTranscript = function() { this.log('show_transcript', {current_time: this.currentTime()}); }; - this.onHideTranscript = function () { + this.onHideTranscript = function() { this.log('hide_transcript', {current_time: this.currentTime()}); }; - this.onShowCaptions = function () { + this.onShowCaptions = function() { this.log('closed_captions.shown', {current_time: this.currentTime()}); }; - this.onHideCaptions = function () { + this.onHideCaptions = function() { this.log('closed_captions.hidden', {current_time: this.currentTime()}); }; - this.logEvent = function (event_type) { - if (this.events.indexOf(event_type) == -1 || typeof this[event_type] !== 'function') { + this.logEvent = function(eventType) { + if (this.events.indexOf(eventType) == -1 || // eslint-disable-line eqeqeq + typeof this[eventType] !== 'function') { return; } - this[event_type](); + this[eventType](); }; - - this.ready(function () { - this.logEvent('onReady') + this.ready(function() { + this.logEvent('onReady'); }) - .on('timeupdate', function () { + .on('timeupdate', function() { previousTime = currentTime; currentTime = this.currentTime(); }) - .on('ratechange', function () { - this.logEvent('onSpeedChange') + .on('ratechange', function() { + this.logEvent('onSpeedChange'); }) - .on('play', function () { - this.logEvent('onPlay'); + .on('play', function() { + this.logEvent('onPlay'); }) - .on('pause', function () { - this.logEvent('onPause') + .on('pause', function() { + this.logEvent('onPause'); }) - .on('ended', function () { - this.logEvent('onEnded') + .on('ended', function() { + this.logEvent('onEnded'); }) - .on('seeked', function () { - this.logEvent('onSeek') + .on('seeked', function() { + this.logEvent('onSeek'); }); /* TODO Add following events forwarding to Open edX when respective features are implemented onShowLanguageMenu, onHideLanguageMenu, onShowTranscript, onHideTranscript, onShowCaptions, onHideCaptions */ - this.log = function (eventName, data) { - data = data || {}; - data['eventType'] = 'xblock-video.' + eventName; + this.log = function(eventName, data) { + var xblockUsageId = window.location.hash.slice(1); + data = data || {}; // eslint-disable-line no-param-reassign + data.eventType = 'xblock-video.' + eventName; // eslint-disable-line no-param-reassign parent.postMessage({ - 'action': 'analytics', - 'info': data, - 'xblockUsageId': xblockUsageId - }, document.location.protocol + "//" + document.location.host); + action: 'analytics', + info: data, + xblockUsageId: xblockUsageId + }, document.location.protocol + '//' + document.location.host); }; - return this; - } - window.xblockEventPlugin = XBlockEventPlugin; // add plugin if player has already initialized if (window.videojs) { - window.videojs.plugin('xblockEventPlugin', xblockEventPlugin); + window.videojs.plugin('xblockEventPlugin', xblockEventPlugin); // eslint-disable-line no-undef } - }).call(this);