Make inline editing save to mongo and then update the preview
This commit is contained in:
@@ -327,7 +327,10 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
|
||||
module,
|
||||
"xmodule_edit.html",
|
||||
{
|
||||
'location': descriptor.location.url(),
|
||||
'editor_content': descriptor.get_html(),
|
||||
'editor_type': descriptor.js_module_name,
|
||||
'editor_class': descriptor.__class__.__name__,
|
||||
# TODO (cpennington): Make descriptors know if they have data that can be editng
|
||||
'editable_data': descriptor.definition.get('data'),
|
||||
'editable_class': 'editable' if descriptor.definition.get('data') else '',
|
||||
@@ -396,7 +399,7 @@ def save_item(request):
|
||||
export_to_github(course, "CMS Edit", author_string)
|
||||
|
||||
descriptor = modulestore().get_item(item_location)
|
||||
preview_html = get_module_previews(request, descriptor)
|
||||
preview_html = get_module_previews(request, descriptor)[0]
|
||||
|
||||
return HttpResponse(json.dumps(preview_html))
|
||||
|
||||
|
||||
@@ -5,24 +5,12 @@ class CMS.Models.Module extends Backbone.Model
|
||||
children: ''
|
||||
metadata: {}
|
||||
|
||||
loadModule: (element) ->
|
||||
elt = $(element).find('.xmodule_edit').first()
|
||||
@module = XModule.loadModule(elt)
|
||||
# find the metadata edit region which should be setup server side,
|
||||
# so that we can wire up posting back those changes
|
||||
@metadata_elt = $(element).find('.metadata_edit')
|
||||
|
||||
editUrl: ->
|
||||
"/edit_item?#{$.param(id: @get('id'))}"
|
||||
initialize: (attributes) ->
|
||||
@module = attributes.module
|
||||
@unset('module')
|
||||
delete attributes.module
|
||||
super(attributes)
|
||||
|
||||
save: (args...) ->
|
||||
@set(data: @module.save()) if @module
|
||||
# cdodge: package up metadata which is separated into a number of input fields
|
||||
# there's probably a better way to do this, but at least this lets me continue to move onwards
|
||||
if @metadata_elt
|
||||
_metadata = {}
|
||||
# walk through the set of elments which have the 'xmetadata_name' attribute and
|
||||
# build up a object to pass back to the server on the subsequent POST
|
||||
_metadata[$(el).data("metadata-name")]=el.value for el in $('[data-metadata-name]', @metadata_elt)
|
||||
@set(metadata: _metadata)
|
||||
super(args...)
|
||||
|
||||
@@ -1,60 +1,60 @@
|
||||
class CMS.Views.ModuleEdit extends Backbone.View
|
||||
tagName: 'section'
|
||||
className: 'edit-pane'
|
||||
|
||||
events:
|
||||
'click .cancel': 'cancel'
|
||||
'click .module-edit': 'editSubmodule'
|
||||
'click .save-update': 'save'
|
||||
tagName: 'div'
|
||||
className: 'xmodule_edit'
|
||||
|
||||
initialize: ->
|
||||
@$el.load @model.editUrl(), =>
|
||||
@model.loadModule(@el)
|
||||
@delegate()
|
||||
|
||||
# Load preview modules
|
||||
XModule.loadModules('display')
|
||||
@$children = @$el.find('#sortable')
|
||||
@enableDrag()
|
||||
@$component_editor = @$el.find('.component-editor')
|
||||
@$metadata = @$component_editor.find('.metadata_edit')
|
||||
|
||||
enableDrag: =>
|
||||
# Enable dragging things in the #sortable div (if there is one)
|
||||
if @$children.length > 0
|
||||
@$children.sortable(
|
||||
placeholder: "ui-state-highlight"
|
||||
update: (event, ui) =>
|
||||
@model.set(children: @$children.find('.module-edit').map(
|
||||
(idx, el) -> $(el).data('id')
|
||||
).toArray())
|
||||
)
|
||||
@$children.disableSelection()
|
||||
delegate: ->
|
||||
id = @$el.data('id')
|
||||
|
||||
events = {}
|
||||
events["click .component-editor[data-id=#{ id }] .cancel-button"] = 'cancel'
|
||||
events["click .component-editor[data-id=#{ id }] .save-button"] = 'save'
|
||||
events["click .component-actions[data-id=#{ id }] .edit-button"] = 'edit'
|
||||
|
||||
@delegateEvents(events)
|
||||
|
||||
metadata: ->
|
||||
# cdodge: package up metadata which is separated into a number of input fields
|
||||
# there's probably a better way to do this, but at least this lets me continue to move onwards
|
||||
_metadata = {}
|
||||
|
||||
if @$metadata
|
||||
# walk through the set of elments which have the 'xmetadata_name' attribute and
|
||||
# build up a object to pass back to the server on the subsequent POST
|
||||
_metadata[$(el).data("metadata-name")] = el.value for el in $('[data-metadata-name]', @$metadata)
|
||||
|
||||
_metadata
|
||||
|
||||
save: (event) =>
|
||||
event.preventDefault()
|
||||
@model.save().done((previews) =>
|
||||
@model.save(
|
||||
metadata: @metadata()
|
||||
).done((preview) =>
|
||||
alert("Your changes have been saved.")
|
||||
previews_section = @$el.find('.previews').empty()
|
||||
$.each(previews, (idx, preview) =>
|
||||
preview_wrapper = $('<section/>', class: 'preview').append preview
|
||||
previews_section.append preview_wrapper
|
||||
)
|
||||
|
||||
new_el = $(preview)
|
||||
@$el.replaceWith(new_el)
|
||||
@$el = new_el
|
||||
|
||||
XModule.loadModules('display')
|
||||
@delegate()
|
||||
|
||||
@model.module = XModule.loadModule(@$el)
|
||||
XModule.loadModules(@$el)
|
||||
).fail( ->
|
||||
alert("There was an error saving your changes. Please try again.")
|
||||
)
|
||||
|
||||
cancel: (event) ->
|
||||
event.preventDefault()
|
||||
CMS.popView()
|
||||
@enableDrag()
|
||||
@$el.removeClass('editing')
|
||||
@$component_editor.slideUp(150)
|
||||
|
||||
editSubmodule: (event) ->
|
||||
edit: (event) ->
|
||||
event.preventDefault()
|
||||
previewType = $(event.target).data('preview-type')
|
||||
moduleType = $(event.target).data('type')
|
||||
CMS.pushView new CMS.Views.ModuleEdit
|
||||
model: new CMS.Models.Module
|
||||
id: $(event.target).data('id')
|
||||
type: if moduleType == 'None' then null else moduleType
|
||||
previewType: if previewType == 'None' then null else previewType
|
||||
@enableDrag()
|
||||
@$el.addClass('editing')
|
||||
@$component_editor.slideDown(150)
|
||||
|
||||
@@ -6,162 +6,175 @@ var $newComponentStep1;
|
||||
var $newComponentStep2;
|
||||
|
||||
$(document).ready(function() {
|
||||
$body = $('body');
|
||||
$modal = $('.history-modal');
|
||||
$modalCover = $('.modal-cover');
|
||||
$newComponentItem = $('.new-component-item');
|
||||
$newComponentStep1 = $('.new-component-step-1');
|
||||
$newComponentStep2 = $('.new-component-step-2');
|
||||
$newComponentButton = $('.new-component-button');
|
||||
$body = $('body');
|
||||
$modal = $('.history-modal');
|
||||
$modalCover = $('.modal-cover');
|
||||
$newComponentItem = $('.new-component-item');
|
||||
$newComponentStep1 = $('.new-component-step-1');
|
||||
$newComponentStep2 = $('.new-component-step-2');
|
||||
$newComponentButton = $('.new-component-button');
|
||||
|
||||
$('.expand-collapse-icon').bind('click', toggleSubmodules);
|
||||
$('.visibility-options').bind('change', setVisibility);
|
||||
$(document).bind('XModule.loaded', function(e, element) {
|
||||
if ($(element).hasClass('.xmodule_display')) {
|
||||
return
|
||||
}
|
||||
var previewType = $(element).data('preview-type');
|
||||
var moduleType = $(element).data('type');
|
||||
new CMS.Views.ModuleEdit({
|
||||
el: element,
|
||||
model: new CMS.Models.Module({
|
||||
module: $(element).data('module'),
|
||||
id: $(element).data('id'),
|
||||
type: moduleType == 'None' ? null : moduleType,
|
||||
previewType: previewType == 'None' ? null : previewType,
|
||||
})
|
||||
});
|
||||
});
|
||||
XModule.loadModules()
|
||||
|
||||
$body.delegate('.xmodule_edit .edit-button', 'click', editComponent);
|
||||
$body.delegate('.component-editor .save-button, .component-editor .cancel-button', 'click', closeComponentEditor);
|
||||
$('.expand-collapse-icon').bind('click', toggleSubmodules);
|
||||
$('.visibility-options').bind('change', setVisibility);
|
||||
|
||||
$newComponentButton.bind('click', showNewComponentForm);
|
||||
$newComponentStep1.find('.new-component-type a').bind('click', showNewComponentProperties);
|
||||
$newComponentStep2.find('.save-button').bind('click', saveNewComponent);
|
||||
$newComponentStep2.find('.cancel-button').bind('click', cancelNewComponent);
|
||||
$newComponentButton.bind('click', showNewComponentForm);
|
||||
$newComponentStep1.find('.new-component-type a').bind('click', showNewComponentProperties);
|
||||
$newComponentStep2.find('.save-button').bind('click', saveNewComponent);
|
||||
$newComponentStep2.find('.cancel-button').bind('click', cancelNewComponent);
|
||||
|
||||
$('.unit-history ol a').bind('click', showHistoryModal);
|
||||
$modal.bind('click', hideHistoryModal);
|
||||
$modalCover.bind('click', hideHistoryModal);
|
||||
|
||||
XModule.loadModules('display');
|
||||
$('.unit-history ol a').bind('click', showHistoryModal);
|
||||
$modal.bind('click', hideHistoryModal);
|
||||
$modalCover.bind('click', hideHistoryModal);
|
||||
});
|
||||
|
||||
function toggleSubmodules(e) {
|
||||
e.preventDefault();
|
||||
$(this).toggleClass('expand').toggleClass('collapse');
|
||||
$(this).closest('.branch, .window').toggleClass('collapsed');
|
||||
e.preventDefault();
|
||||
$(this).toggleClass('expand').toggleClass('collapse');
|
||||
$(this).closest('.branch, .window').toggleClass('collapsed');
|
||||
}
|
||||
|
||||
function setVisibility(e) {
|
||||
$(this).find('.checked').removeClass('checked');
|
||||
$(e.target).closest('.option').addClass('checked');
|
||||
$(this).find('.checked').removeClass('checked');
|
||||
$(e.target).closest('.option').addClass('checked');
|
||||
}
|
||||
|
||||
function editComponent(e) {
|
||||
e.preventDefault();
|
||||
$(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
|
||||
e.preventDefault();
|
||||
$(this).closest('.xmodule_edit').addClass('editing').find('.component-editor').slideDown(150);
|
||||
}
|
||||
|
||||
function closeComponentEditor(e) {
|
||||
e.preventDefault();
|
||||
$(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
|
||||
e.preventDefault();
|
||||
$(this).closest('.xmodule_edit').removeClass('editing').find('.component-editor').slideUp(150);
|
||||
}
|
||||
|
||||
function showNewComponentForm(e) {
|
||||
e.preventDefault();
|
||||
$newComponentItem.addClass('adding');
|
||||
$(this).slideUp(150);
|
||||
$newComponentStep1.slideDown(150);
|
||||
e.preventDefault();
|
||||
$newComponentItem.addClass('adding');
|
||||
$(this).slideUp(150);
|
||||
$newComponentStep1.slideDown(150);
|
||||
}
|
||||
|
||||
function showNewComponentProperties(e) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
var displayName;
|
||||
var componentSource;
|
||||
var selectionRange;
|
||||
var $renderedComponent;
|
||||
var displayName;
|
||||
var componentSource;
|
||||
var selectionRange;
|
||||
var $renderedComponent;
|
||||
|
||||
switch($(this).attr('data-type')) {
|
||||
case 'video':
|
||||
displayName = 'Video';
|
||||
componentSource = '<video youtube="1.50:___,1.25:___,1.0:___,0.75:___"/>';
|
||||
selectionRange = [21, 24];
|
||||
$renderedComponent = $('<div class="rendered-component"><div class="video-unit"><img src="images/video-module.png"></div></div>');
|
||||
break;
|
||||
case 'textbook':
|
||||
displayName = 'Textbook';
|
||||
componentSource = '<customtag page="___"><impl>book</impl></customtag>';
|
||||
selectionRange = [17, 20];
|
||||
$renderedComponent = $('<div class="rendered-component"><p><span class="textbook-icon"></span>More information given in the text.</p></div>');
|
||||
break;
|
||||
case 'slide':
|
||||
displayName = 'Slide';
|
||||
componentSource = '<customtag page="___"><customtag lecnum="___"><impl>slides</impl></customtag>';
|
||||
selectionRange = [17, 20];
|
||||
$renderedComponent = $('<div class="rendered-component"><p><span class="slides-icon"></span>Lecture Slides Handout [Clean] [Annotated]</p></div>');
|
||||
break;
|
||||
case 'discussion':
|
||||
displayName = 'Discussion';
|
||||
componentSource = '<discussion for="___" id="___" discussion_category="___"/>';
|
||||
selectionRange = [17, 20];
|
||||
$renderedComponent = $('<div class="rendered-component"><div class="discussion-unit"><img src="images/discussion-module.png"></div></div>');
|
||||
break;
|
||||
case 'problem':
|
||||
displayName = 'Problem';
|
||||
componentSource = '<problem>___</problem>';
|
||||
selectionRange = [9, 12];
|
||||
$renderedComponent = $('<div class="rendered-component"></div>');
|
||||
break;
|
||||
case 'freeform':
|
||||
displayName = 'Freeform HTML';
|
||||
componentSource = '';
|
||||
selectionRange = [0, 0];
|
||||
$renderedComponent = $('<div class="rendered-component"></div>');
|
||||
break;
|
||||
}
|
||||
switch($(this).attr('data-type')) {
|
||||
case 'video':
|
||||
displayName = 'Video';
|
||||
componentSource = '<video youtube="1.50:___,1.25:___,1.0:___,0.75:___"/>';
|
||||
selectionRange = [21, 24];
|
||||
$renderedComponent = $('<div class="rendered-component"><div class="video-unit"><img src="images/video-module.png"></div></div>');
|
||||
break;
|
||||
case 'textbook':
|
||||
displayName = 'Textbook';
|
||||
componentSource = '<customtag page="___"><impl>book</impl></customtag>';
|
||||
selectionRange = [17, 20];
|
||||
$renderedComponent = $('<div class="rendered-component"><p><span class="textbook-icon"></span>More information given in the text.</p></div>');
|
||||
break;
|
||||
case 'slide':
|
||||
displayName = 'Slide';
|
||||
componentSource = '<customtag page="___"><customtag lecnum="___"><impl>slides</impl></customtag>';
|
||||
selectionRange = [17, 20];
|
||||
$renderedComponent = $('<div class="rendered-component"><p><span class="slides-icon"></span>Lecture Slides Handout [Clean] [Annotated]</p></div>');
|
||||
break;
|
||||
case 'discussion':
|
||||
displayName = 'Discussion';
|
||||
componentSource = '<discussion for="___" id="___" discussion_category="___"/>';
|
||||
selectionRange = [17, 20];
|
||||
$renderedComponent = $('<div class="rendered-component"><div class="discussion-unit"><img src="images/discussion-module.png"></div></div>');
|
||||
break;
|
||||
case 'problem':
|
||||
displayName = 'Problem';
|
||||
componentSource = '<problem>___</problem>';
|
||||
selectionRange = [9, 12];
|
||||
$renderedComponent = $('<div class="rendered-component"></div>');
|
||||
break;
|
||||
case 'freeform':
|
||||
displayName = 'Freeform HTML';
|
||||
componentSource = '';
|
||||
selectionRange = [0, 0];
|
||||
$renderedComponent = $('<div class="rendered-component"></div>');
|
||||
break;
|
||||
}
|
||||
|
||||
$newComponentItem.prepend($renderedComponent);
|
||||
$renderedComponent.slideDown(250);
|
||||
$newComponentItem.prepend($renderedComponent);
|
||||
$renderedComponent.slideDown(250);
|
||||
|
||||
$newComponentStep2.find('h5').html('Edit ' + displayName + ' Component');
|
||||
$newComponentStep2.find('textarea').html(componentSource);
|
||||
setTimeout(function() {
|
||||
$newComponentStep2.find('textarea').focus().get(0).setSelectionRange(selectionRange[0], selectionRange[1]);
|
||||
}, 10);
|
||||
$newComponentStep2.find('h5').html('Edit ' + displayName + ' Component');
|
||||
$newComponentStep2.find('textarea').html(componentSource);
|
||||
setTimeout(function() {
|
||||
$newComponentStep2.find('textarea').focus().get(0).setSelectionRange(selectionRange[0], selectionRange[1]);
|
||||
}, 10);
|
||||
|
||||
$newComponentStep1.slideUp(250);
|
||||
$newComponentStep2.slideDown(250);
|
||||
$newComponentStep1.slideUp(250);
|
||||
$newComponentStep2.slideDown(250);
|
||||
}
|
||||
|
||||
function cancelNewComponent(e) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
$newComponentStep2.slideUp(250);
|
||||
$newComponentButton.slideDown(250);
|
||||
$newComponentItem.removeClass('adding');
|
||||
$newComponentItem.find('.rendered-component').remove();
|
||||
$newComponentStep2.slideUp(250);
|
||||
$newComponentButton.slideDown(250);
|
||||
$newComponentItem.removeClass('adding');
|
||||
$newComponentItem.find('.rendered-component').remove();
|
||||
}
|
||||
|
||||
function saveNewComponent(e) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
var $newComponent = $newComponentItem.clone();
|
||||
$newComponent.removeClass('adding').removeClass('new-component-item');
|
||||
$newComponent.find('.new-component-step-2').removeClass('new-component-step-2').addClass('component-editor');
|
||||
setTimeout(function() {
|
||||
$newComponent.find('.component-editor').slideUp(250);
|
||||
}, 10);
|
||||
$newComponent.append('<div class="component-actions"><a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a><a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a> </div><a href="#" class="drag-handle"></a>');
|
||||
$newComponent.find('.new-component-step-1').remove();
|
||||
$newComponent.find('.new-component-button').remove();
|
||||
var $newComponent = $newComponentItem.clone();
|
||||
$newComponent.removeClass('adding').removeClass('new-component-item');
|
||||
$newComponent.find('.new-component-step-2').removeClass('new-component-step-2').addClass('component-editor');
|
||||
setTimeout(function() {
|
||||
$newComponent.find('.component-editor').slideUp(250);
|
||||
}, 10);
|
||||
$newComponent.append('<div class="component-actions"><a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a><a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a> </div><a href="#" class="drag-handle"></a>');
|
||||
$newComponent.find('.new-component-step-1').remove();
|
||||
$newComponent.find('.new-component-button').remove();
|
||||
|
||||
$newComponentStep2.slideUp(250);
|
||||
$newComponentButton.slideDown(250);
|
||||
$newComponentItem.removeClass('adding');
|
||||
$newComponentItem.find('.rendered-component').remove();
|
||||
$newComponentStep2.slideUp(250);
|
||||
$newComponentButton.slideDown(250);
|
||||
$newComponentItem.removeClass('adding');
|
||||
$newComponentItem.find('.rendered-component').remove();
|
||||
|
||||
$newComponentItem.before($newComponent);
|
||||
$newComponentItem.before($newComponent);
|
||||
}
|
||||
|
||||
function showHistoryModal(e) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
$modal.show();
|
||||
$modalCover.show();
|
||||
$modal.show();
|
||||
$modalCover.show();
|
||||
}
|
||||
|
||||
function hideHistoryModal(e) {
|
||||
e.preventDefault();
|
||||
e.preventDefault();
|
||||
|
||||
$modal.hide();
|
||||
$modalCover.hide();
|
||||
$modal.hide();
|
||||
$modalCover.hide();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class @Sequence
|
||||
|
||||
@mark_active new_position
|
||||
@$('#seq_content').html @contents.eq(new_position - 1).text()
|
||||
XModule.loadModules('display', @$('#seq_content'))
|
||||
XModule.loadModules(@$('#seq_content'))
|
||||
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, "seq_content"]) # NOTE: Actually redundant. Some other MathJax call also being performed
|
||||
window.update_schematics() # For embedded circuit simulator exercises in 6.002x
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
@XModule =
|
||||
###
|
||||
Load a single module (either an edit module or a display module)
|
||||
from the supplied element, which should have a data-type attribute
|
||||
specifying the class to load
|
||||
###
|
||||
loadModule: (element) ->
|
||||
moduleType = $(element).data('type')
|
||||
if moduleType == 'None'
|
||||
return
|
||||
###
|
||||
Load a single module (either an edit module or a display module)
|
||||
from the supplied element, which should have a data-type attribute
|
||||
specifying the class to load
|
||||
###
|
||||
loadModule: (element) ->
|
||||
moduleType = $(element).data('type')
|
||||
if moduleType == 'None'
|
||||
return
|
||||
|
||||
try
|
||||
new window[moduleType](element)
|
||||
catch error
|
||||
console.error "Unable to load #{moduleType}: #{error.message}" if console
|
||||
try
|
||||
$(element).data('module', new window[moduleType](element))
|
||||
$(document).trigger('XModule.loaded', [element])
|
||||
catch error
|
||||
console.error "Unable to load #{moduleType}: #{error.message}" if console
|
||||
|
||||
###
|
||||
Load all modules on the page of the specified type.
|
||||
If container is provided, only load modules inside that element
|
||||
Type is one of 'display' or 'edit'
|
||||
###
|
||||
loadModules: (type, container) ->
|
||||
selector = ".xmodule_#{type}"
|
||||
###
|
||||
Load all modules on the page of the specified type.
|
||||
If container is provided, only load modules inside that element
|
||||
Type is one of 'display' or 'edit'
|
||||
###
|
||||
loadModules: (container) ->
|
||||
selector = ".xmodule_edit, .xmodule_display"
|
||||
|
||||
if container?
|
||||
modules = $(container).find(selector)
|
||||
else
|
||||
modules = $(selector)
|
||||
if container?
|
||||
modules = $(container).find(selector)
|
||||
else
|
||||
modules = $(selector)
|
||||
|
||||
modules.each (idx, element) -> XModule.loadModule element
|
||||
modules.each (idx, element) -> XModule.loadModule element
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<div class="xmodule_edit xmodule_${class_} ${editable_class}" data-type="${module_name}">
|
||||
<div class="xmodule_edit xmodule_${editor_class} ${editable_class}" data-type="${editor_type}" data-id="${location}">
|
||||
<%include file="xmodule_display.html"/>
|
||||
% if editable_data:
|
||||
<div class="component-actions">
|
||||
<div class="component-actions" data-id="${location}">
|
||||
<a href="#" class="edit-button"><span class="edit-icon white"></span>Edit</a>
|
||||
<a href="#" class="delete-button"><span class="delete-icon white"></span>Delete</a>
|
||||
</div>
|
||||
<div class="component-editor">
|
||||
<div class="component-editor" data-id="${location}">
|
||||
${editor_content}
|
||||
<a href="#" class="save-button">Save</a>
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
|
||||
@@ -11,7 +11,7 @@ class @Courseware
|
||||
new Courseware
|
||||
|
||||
render: ->
|
||||
XModule.loadModules('display')
|
||||
XModule.loadModules()
|
||||
$('.course-content .histogram').each ->
|
||||
id = $(this).attr('id').replace(/histogram_/, '')
|
||||
try
|
||||
|
||||
Reference in New Issue
Block a user