Files
edx-platform/cms/static/js/views/xblock.js
Dmitry Viskov 209ddc700d Difficulty selectbox in Studio (based on new XBlockAside functionality). Include:
- adaptation asides to be imported from the XML
- updating SplitMongo to handle XBlockAsides (CRUD operations)
- updating Studio to handle XBlockAsides handler calls
- updating xblock/core.js to properly init XBlockAsides JavaScript
2016-03-02 03:37:13 +03:00

237 lines
10 KiB
JavaScript

define(["jquery", "underscore", "common/js/components/utils/view_utils", "js/views/baseview", "xblock/runtime.v1"],
function ($, _, ViewUtils, BaseView, XBlock) {
'use strict';
var XBlockView = BaseView.extend({
// takes XBlockInfo as a model
events: {
"click .notification-action-button": "fireNotificationActionEvent"
},
initialize: function() {
BaseView.prototype.initialize.call(this);
this.view = this.options.view;
},
render: function(options) {
var self = this,
view = this.view,
xblockInfo = this.model,
xblockUrl = xblockInfo.url();
return $.ajax({
url: decodeURIComponent(xblockUrl) + "/" + view,
type: 'GET',
cache: false,
headers: { Accept: 'application/json' },
success: function(fragment) {
self.handleXBlockFragment(fragment, options);
}
});
},
initRuntimeData: function(xblock, options) {
if (options && options.initRuntimeData && xblock && xblock.runtime && !xblock.runtime.page) {
xblock.runtime.page = options.initRuntimeData;
}
return xblock;
},
handleXBlockFragment: function(fragment, options) {
var self = this,
wrapper = this.$el,
xblockElement,
successCallback = options ? options.success || options.done : null,
errorCallback = options ? options.error || options.done : null,
xblock,
fragmentsRendered;
fragmentsRendered = this.renderXBlockFragment(fragment, wrapper);
fragmentsRendered.always(function() {
xblockElement = self.$('.xblock').first();
try {
xblock = XBlock.initializeBlock(xblockElement);
self.xblock = self.initRuntimeData(xblock, options);
self.xblockReady(self.xblock);
self.$('.xblock_asides-v1').each(function() {
if (!$(this).hasClass('xblock-initialized')) {
var aside = XBlock.initializeBlock($(this));
self.initRuntimeData(aside, options);
}
});
if (successCallback) {
successCallback(xblock);
}
} catch (e) {
console.error(e.stack);
// Add 'xblock-initialization-failed' class to every xblock
self.$('.xblock').addClass('xblock-initialization-failed');
// If the xblock was rendered but failed then still call xblockReady to allow
// drag-and-drop to be initialized.
if (xblockElement) {
self.xblockReady(null);
}
if (errorCallback) {
errorCallback();
}
}
});
},
/**
* Sends a notification event to the runtime, if one is available. Note that the runtime
* is only available once the xblock has been rendered and successfully initialized.
* @param eventName The name of the event to be fired.
* @param data The data to be passed to any listener's of the event.
*/
notifyRuntime: function(eventName, data) {
var runtime = this.xblock && this.xblock.runtime;
if (runtime) {
runtime.notify(eventName, data);
} else if (this.xblock) {
var xblock_children = this.xblock.element && $(this.xblock.element).prop('xblock_children');
if (xblock_children) {
$(xblock_children).each(function () {
if (this.runtime) {
this.runtime.notify(eventName, data);
}
});
}
}
},
/**
* This method is called upon successful rendering of an xblock. Note that the xblock
* may have thrown JavaScript errors after rendering in which case the xblock parameter
* will be null.
*/
xblockReady: function(xblock) { // jshint ignore:line
// Do nothing
},
/**
* Renders an xblock fragment into the specified element. The fragment has two attributes:
* html: the HTML to be rendered
* resources: any JavaScript or CSS resources that the HTML depends upon
* Note that the XBlock is rendered asynchronously, and so a promise is returned that
* represents this process.
* @param fragment The fragment returned from the xblock_handler
* @param element The element into which to render the fragment (defaults to this.$el)
* @returns {Promise} A promise representing the rendering process
*/
renderXBlockFragment: function(fragment, element) {
var html = fragment.html,
resources = fragment.resources;
if (!element) {
element = this.$el;
}
// Render the HTML first as the scripts might depend upon it, and then
// asynchronously add the resources to the page. Any errors that are thrown
// by included scripts are logged to the console but are then ignored assuming
// that at least the rendered HTML will be in place.
try {
this.updateHtml(element, html);
return this.addXBlockFragmentResources(resources);
} catch(e) {
console.error(e.stack);
return $.Deferred().resolve();
}
},
/**
* Updates an element to have the specified HTML. The default method sets the HTML
* as child content, but this can be overridden.
* @param element The element to be updated
* @param html The desired HTML.
*/
updateHtml: function(element, html) {
element.html(html);
},
/**
* Dynamically loads all of an XBlock's dependent resources. This is an asynchronous
* process so a promise is returned.
* @param resources The resources to be rendered
* @returns {Promise} A promise representing the rendering process
*/
addXBlockFragmentResources: function(resources) {
var self = this,
applyResource,
numResources,
deferred;
numResources = resources.length;
deferred = $.Deferred();
applyResource = function(index) {
var hash, resource, value, promise;
if (index >= numResources) {
deferred.resolve();
return;
}
value = resources[index];
hash = value[0];
if (!window.loadedXBlockResources) {
window.loadedXBlockResources = [];
}
if (_.indexOf(window.loadedXBlockResources, hash) < 0) {
resource = value[1];
promise = self.loadResource(resource);
window.loadedXBlockResources.push(hash);
promise.done(function() {
applyResource(index + 1);
}).fail(function() {
deferred.reject();
});
} else {
applyResource(index + 1);
}
};
applyResource(0);
return deferred.promise();
},
/**
* Loads the specified resource into the page.
* @param resource The resource to be loaded.
* @returns {Promise} A promise representing the loading of the resource.
*/
loadResource: function(resource) {
var head = $('head'),
mimetype = resource.mimetype,
kind = resource.kind,
placement = resource.placement,
data = resource.data;
if (mimetype === "text/css") {
if (kind === "text") {
head.append("<style type='text/css'>" + data + "</style>");
} else if (kind === "url") {
head.append("<link rel='stylesheet' href='" + data + "' type='text/css'>");
}
} else if (mimetype === "application/javascript") {
if (kind === "text") {
head.append("<script>" + data + "</script>");
} else if (kind === "url") {
return ViewUtils.loadJavaScript(data);
}
} else if (mimetype === "text/html") {
if (placement === "head") {
head.append(data);
}
}
// Return an already resolved promise for synchronous updates
return $.Deferred().resolve().promise();
},
fireNotificationActionEvent: function(event) {
var eventName = $(event.currentTarget).data("notification-action");
if (eventName) {
event.preventDefault();
this.notifyRuntime(eventName, this.model.get("id"));
}
}
});
return XBlockView;
}); // end define();