/** * Provides utilities for views to work with xblocks. */ define(['jquery', 'underscore', 'gettext', 'common/js/components/utils/view_utils', 'js/utils/module', 'js/models/xblock_info', 'edx-ui-toolkit/js/utils/string-utils'], function($, _, gettext, ViewUtils, ModuleUtils, XBlockInfo, StringUtils) { 'use strict'; var addXBlock, duplicateXBlock, deleteXBlock, createUpdateRequestData, updateXBlockField, VisibilityState, getXBlockVisibilityClass, getXBlockListTypeClass, updateXBlockFields, getXBlockType, findXBlockInfo, moveXBlock; /** * Represents the possible visibility states for an xblock: * * live - the block and all of its descendants are live to students (excluding staff only) * Note: Live means both published and released. * * ready - the block is ready to go live and all of its descendants are live or ready (excluding staff only) * Note: content is ready when it is published and scheduled with a release date in the future. * * unscheduled - the block and all of its descendants have no release date (excluding staff only) * Note: it is valid for items to be published with no release date in which case they are unscheduled. * * needsAttention - the block or its descendants need attention * i.e. there is some content that is not fully live, ready, unscheduled or staff only. * For example: one subsection has draft content, or there's both unreleased and released content * in one section. * * staffOnly - all of the block's content is to be shown to staff only * Note: staff only items do not affect their parent's state. */ VisibilityState = { live: 'live', ready: 'ready', unscheduled: 'unscheduled', needsAttention: 'needs_attention', staffOnly: 'staff_only', gated: 'gated' }; /** * Adds an xblock based upon the data attributes of the specified add button. A promise * is returned, and the new locator is passed to all done handlers. * @param target The add button that was clicked upon. * @returns {jQuery promise} A promise representing the addition of the xblock. */ addXBlock = function(target) { var parentLocator = target.data('parent'), category = target.data('category'), displayName = target.data('default-name'); return ViewUtils.runOperationShowingMessage(gettext('Adding'), function() { var addOperation = $.Deferred(); analytics.track('Created a ' + category, { 'course': course_location_analytics, 'display_name': displayName }); $.postJSON(ModuleUtils.getUpdateUrl(), { 'parent_locator': parentLocator, 'category': category, 'display_name': displayName }, function(data) { var locator = data.locator; addOperation.resolve(locator); }); return addOperation.promise(); }); }; /** * Duplicates the specified xblock element in its parent xblock. * @param {jquery Element} xblockElement The xblock element to be duplicated. * @param {jquery Element} parentElement Parent element of the xblock element to be duplicated, * new duplicated xblock would be placed under this xblock. * @returns {jQuery promise} A promise representing the duplication of the xblock. */ duplicateXBlock = function(xblockElement, parentElement) { return ViewUtils.runOperationShowingMessage(gettext('Duplicating'), function() { var duplicationOperation = $.Deferred(); $.postJSON(ModuleUtils.getUpdateUrl(), { duplicate_source_locator: xblockElement.data('locator'), parent_locator: parentElement.data('locator') }, function(data) { duplicationOperation.resolve(data); }) .fail(function() { duplicationOperation.reject(); }); return duplicationOperation.promise(); }); }; /** * Moves the specified xblock in a new parent xblock. * @param {String} sourceLocator Locator of xblock element to be moved. * @param {String} targetParentLocator Locator of the target parent xblock, moved xblock would be placed * under this xblock. * @param {Integer} targetIndex Intended index position of the xblock in parent xblock. If provided, * xblock would be placed at the particular index in the parent xblock. * @returns {jQuery promise} A promise representing the moving of the xblock. */ moveXBlock = function(sourceLocator, targetParentLocator, targetIndex) { var moveOperation = $.Deferred(), operationText = targetIndex !== undefined ? gettext('Undo moving') : gettext('Moving'); return ViewUtils.runOperationShowingMessage(operationText, function() { $.patchJSON(ModuleUtils.getUpdateUrl(), { move_source_locator: sourceLocator, parent_locator: targetParentLocator, target_index: targetIndex }, function(response) { moveOperation.resolve(response); }) .fail(function() { moveOperation.reject(); }); return moveOperation.promise(); }); }; /** * Deletes the specified xblock. * @param xblockInfo The model for the xblock to be deleted. * @param xblockType A string representing the type of the xblock to be deleted. * @returns {jQuery promise} A promise representing the deletion of the xblock. */ deleteXBlock = function(xblockInfo, xblockType) { var deletion = $.Deferred(), url = ModuleUtils.getUpdateUrl(xblockInfo.id), operation = function() { ViewUtils.runOperationShowingMessage(gettext('Deleting'), function() { return $.ajax({ type: 'DELETE', url: url }).success(function() { deletion.resolve(); }); } ); }, messageBody; xblockType = xblockType || 'component'; // eslint-disable-line no-param-reassign messageBody = StringUtils.interpolate( gettext('Deleting this {xblock_type} is permanent and cannot be undone.'), {xblock_type: xblockType}, true ); if (xblockInfo.get('is_prereq')) { messageBody += ' ' + gettext('Any content that has listed this content as a prerequisite will also have access limitations removed.'); // eslint-disable-line max-len ViewUtils.confirmThenRunOperation( StringUtils.interpolate( gettext('Delete this {xblock_type} (and prerequisite)?'), {xblock_type: xblockType}, true ), messageBody, StringUtils.interpolate( gettext('Yes, delete this {xblock_type}'), {xblock_type: xblockType}, true ), operation ); } else { ViewUtils.confirmThenRunOperation( StringUtils.interpolate( gettext('Delete this {xblock_type}?'), {xblock_type: xblockType}, true ), messageBody, StringUtils.interpolate( gettext('Yes, delete this {xblock_type}'), {xblock_type: xblockType}, true ), operation ); } return deletion.promise(); }; createUpdateRequestData = function(fieldName, newValue) { var metadata = {}; metadata[fieldName] = newValue; return { metadata: metadata }; }; /** * Updates the specified field of an xblock to a new value. * @param {Backbone Model} xblockInfo The XBlockInfo model representing the xblock. * @param {String} fieldName The xblock field name to be updated. * @param {*} newValue The new value for the field. * @returns {jQuery promise} A promise representing the updating of the field. */ updateXBlockField = function(xblockInfo, fieldName, newValue) { var requestData = createUpdateRequestData(fieldName, newValue); return ViewUtils.runOperationShowingMessage(gettext('Saving'), function() { return xblockInfo.save(requestData, {patch: true}); }); }; /** * Updates the specified fields of an xblock to a new values. * @param {Backbone Model} xblockInfo The XBlockInfo model representing the xblock. * @param {Object} xblockData Object representing xblock data as accepted on server. * @param {Object} [options] Hash with options. * @returns {jQuery promise} A promise representing the updating of the xblock values. */ updateXBlockFields = function(xblockInfo, xblockData, options) { options = _.extend({}, {patch: true}, options); return ViewUtils.runOperationShowingMessage(gettext('Saving'), function() { return xblockInfo.save(xblockData, options); } ); }; /** * Returns the CSS class to represent the specified xblock visibility state. */ getXBlockVisibilityClass = function(visibilityState) { if (visibilityState === VisibilityState.staffOnly) { return 'is-staff-only'; } if (visibilityState === VisibilityState.gated) { return 'is-gated'; } if (visibilityState === VisibilityState.live) { return 'is-live'; } if (visibilityState === VisibilityState.ready) { return 'is-ready'; } if (visibilityState === VisibilityState.needsAttention) { return 'has-warnings'; } return ''; }; getXBlockListTypeClass = function(xblockType) { var listType = 'list-unknown'; if (xblockType === 'course') { listType = 'list-sections'; } else if (xblockType === 'section') { listType = 'list-subsections'; } else if (xblockType === 'subsection') { listType = 'list-units'; } return listType; }; getXBlockType = function(category, parentInfo, translate) { var xblockType = category; if (category === 'chapter') { xblockType = translate ? gettext('section') : 'section'; } else if (category === 'sequential') { xblockType = translate ? gettext('subsection') : 'subsection'; } else if (category === 'vertical' && (!parentInfo || parentInfo.get('category') === 'sequential')) { xblockType = translate ? gettext('unit') : 'unit'; } return xblockType; }; findXBlockInfo = function(xblockWrapperElement, defaultXBlockInfo) { var xblockInfo = defaultXBlockInfo, xblockElement, displayName, hasChildren; if (xblockWrapperElement.length > 0) { xblockElement = xblockWrapperElement.find('.xblock'); displayName = xblockWrapperElement.find( '.xblock-header .header-details .xblock-display-name' ).text().trim(); // If not found, try looking for the old unit page style rendering. // Only used now by static pages. if (!displayName) { displayName = xblockElement.find('.component-header').text().trim(); } hasChildren = defaultXBlockInfo ? defaultXBlockInfo.get('has_children') : false; xblockInfo = new XBlockInfo({ id: xblockWrapperElement.data('locator'), courseKey: xblockWrapperElement.data('course-key'), category: xblockElement.data('block-type'), display_name: displayName, has_children: hasChildren }); } return xblockInfo; }; return { VisibilityState: VisibilityState, addXBlock: addXBlock, moveXBlock: moveXBlock, duplicateXBlock: duplicateXBlock, deleteXBlock: deleteXBlock, updateXBlockField: updateXBlockField, getXBlockVisibilityClass: getXBlockVisibilityClass, getXBlockListTypeClass: getXBlockListTypeClass, updateXBlockFields: updateXBlockFields, getXBlockType: getXBlockType, findXBlockInfo: findXBlockInfo }; });