Change save_item and create_item to RESTful URL.

Part of STUD-847.
This commit is contained in:
cahrens
2013-11-06 10:50:00 -05:00
parent dffbb8a37d
commit a170c6f4e7
42 changed files with 546 additions and 489 deletions

View File

@@ -182,7 +182,7 @@ define([
"coffee/spec/main_spec",
"coffee/spec/models/course_spec", "coffee/spec/models/metadata_spec",
"coffee/spec/models/module_spec", "coffee/spec/models/section_spec",
"coffee/spec/models/section_spec",
"coffee/spec/models/settings_course_grader_spec",
"coffee/spec/models/settings_grading_spec", "coffee/spec/models/textbook_spec",
"coffee/spec/models/upload_spec",
@@ -193,9 +193,11 @@ define([
"coffee/spec/views/overview_spec",
"coffee/spec/views/textbook_spec", "coffee/spec/views/upload_spec",
"js_spec/transcripts/utils_spec", "js_spec/transcripts/editor_spec",
"js_spec/transcripts/videolist_spec", "js_spec/transcripts/message_manager_spec",
"js_spec/transcripts/file_uploader_spec"
"js/spec/transcripts/utils_spec", "js/spec/transcripts/editor_spec",
"js/spec/transcripts/videolist_spec", "js/spec/transcripts/message_manager_spec",
"js/spec/transcripts/file_uploader_spec",
"js/spec/utils/module_spec"
# these tests are run separate in the cms-squire suite, due to process
# isolation issues with Squire.js

View File

@@ -1,7 +0,0 @@
define ["coffee/src/models/module"], (Module) ->
describe "Module", ->
it "set the correct URL", ->
expect(new Module().url).toEqual("/save_item")
it "set the correct default", ->
expect(new Module().defaults).toEqual(undefined)

View File

@@ -1,9 +1,9 @@
define ["js/models/section", "sinon"], (Section, sinon) ->
define ["js/models/section", "sinon", "js/utils/module"], (Section, sinon, ModuleUtils) ->
describe "Section", ->
describe "basic", ->
beforeEach ->
@model = new Section({
id: 42,
id: 42
name: "Life, the Universe, and Everything"
})
@@ -14,11 +14,10 @@ define ["js/models/section", "sinon"], (Section, sinon) ->
expect(@model.get("name")).toEqual("Life, the Universe, and Everything")
it "should have a URL set", ->
expect(@model.url).toEqual("/save_item")
expect(@model.url()).toEqual(ModuleUtils.getUpdateUrl(42))
it "should serialize to JSON correctly", ->
expect(@model.toJSON()).toEqual({
id: 42,
metadata:
{
display_name: "Life, the Universe, and Everything"
@@ -30,7 +29,7 @@ define ["js/models/section", "sinon"], (Section, sinon) ->
spyOn(Section.prototype, 'showNotification')
spyOn(Section.prototype, 'hideNotification')
@model = new Section({
id: 42,
id: 42
name: "Life, the Universe, and Everything"
})
@requests = requests = []

View File

@@ -4,6 +4,9 @@ define ["coffee/src/views/module_edit", "xmodule"], (ModuleEdit) ->
beforeEach ->
@stubModule = jasmine.createSpy("Module")
@stubModule.id = 'stub-id'
@stubModule.get = (param)->
if param == 'old_id'
return 'stub-old-id'
setFixtures """
<li class="component" id="stub-id">
@@ -59,7 +62,7 @@ define ["coffee/src/views/module_edit", "xmodule"], (ModuleEdit) ->
@moduleEdit.render()
it "loads the module preview and editor via ajax on the view element", ->
expect(@moduleEdit.$el.load).toHaveBeenCalledWith("/preview_component/#{@moduleEdit.model.id}", jasmine.any(Function))
expect(@moduleEdit.$el.load).toHaveBeenCalledWith("/preview_component/#{@moduleEdit.model.get('old_id')}", jasmine.any(Function))
@moduleEdit.$el.load.mostRecentCall.args[1]()
expect(@moduleEdit.loadDisplay).toHaveBeenCalled()
expect(@moduleEdit.delegateEvents).toHaveBeenCalled()

View File

@@ -8,7 +8,7 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
<span class="published-status">
<strong>Will Release:</strong> 06/12/2013 at 04:00 UTC
</span>
<a href="#" class="edit-button" data-date="06/12/2013" data-time="04:00" data-id="i4x://pfogg/42/chapter/d6b47f7b084f49debcaf67fe5436c8e2">Edit</a>
<a href="#" class="edit-button" data-date="06/12/2013" data-time="04:00" data-locator="i4x://pfogg/42/chapter/d6b47f7b084f49debcaf67fe5436c8e2">Edit</a>
</div>
"""
@@ -35,8 +35,8 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
"""
appendSetFixtures """
<section class="courseware-section branch" data-id="a-location-goes-here">
<li class="branch collapsed id-holder" data-id="an-id-goes-here">
<section class="courseware-section branch" data-locator="a-location-goes-here">
<li class="branch collapsed id-holder" data-id="an-id-goes-here" data-locator="an-id-goes-here">
<a href="#" class="delete-section-button"></a>
</li>
</section>
@@ -44,19 +44,19 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
appendSetFixtures """
<ol>
<li class="subsection-list branch" data-id="subsection-1-id" id="subsection-1">
<li class="subsection-list branch" id="subsection-1" data-locator="subsection-1-id">
<ol class="sortable-unit-list" id="subsection-list-1">
<li class="unit" id="unit-1" data-id="first-unit-id" data-parent-id="subsection-1-id"></li>
<li class="unit" id="unit-2" data-id="second-unit-id" data-parent-id="subsection-1-id"></li>
<li class="unit" id="unit-3" data-id="third-unit-id" data-parent-id="subsection-1-id"></li>
<li class="unit" id="unit-1" data-parent="subsection-1-id" data-locator="first-unit-id"></li>
<li class="unit" id="unit-2" data-parent="subsection-1-id" data-locator="second-unit-id"></li>
<li class="unit" id="unit-3" data-parent="subsection-1-id" data-locator="third-unit-id"></li>
</ol>
</li>
<li class="subsection-list branch" data-id="subsection-2-id" id="subsection-2">
<li class="subsection-list branch" id="subsection-2" data-locator="subsection-2-id">
<ol class="sortable-unit-list" id="subsection-list-2">
<li class="unit" id="unit-4" data-id="fourth-unit-id" data-parent-id="subsection-2"></li>
<li class="unit" id="unit-4" data-parent="subsection-2" data-locator="fourth-unit-id"></li>
</ol>
</li>
<li class="subsection-list branch" data-id="subsection-3-id" id="subsection-3">
<li class="subsection-list branch" id="subsection-3" data-locator="subsection-3-id">
<ol class="sortable-unit-list" id="subsection-list-3">
</li>
</ol>
@@ -366,10 +366,10 @@ define ["js/views/overview", "js/views/feedback_notification", "sinon", "js/base
expect($('#unit-1')).toHaveClass('was-dropped')
# We expect 2 requests to be sent-- the first for removing Unit 1 from Subsection 1,
# and the second for adding Unit 1 to the end of Subsection 2.
expect(@requests[0].requestBody).toEqual('{"id":"subsection-1-id","children":["second-unit-id","third-unit-id"]}')
expect(@requests[0].requestBody).toEqual('{"children":["second-unit-id","third-unit-id"]}')
@requests[0].respond(200)
expect(@savingSpies.hide).not.toHaveBeenCalled()
expect(@requests[1].requestBody).toEqual('{"id":"subsection-2-id","children":["fourth-unit-id","first-unit-id"]}')
expect(@requests[1].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}')
@requests[1].respond(200)
expect(@savingSpies.hide).toHaveBeenCalled()
# Class is removed in a timeout.

View File

@@ -1,4 +0,0 @@
define ["backbone"], (Backbone) ->
class Module extends Backbone.Model
url: '/save_item'

View File

@@ -63,20 +63,21 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
return _.extend(@metadataEditor.getModifiedMetadataValues(), @customMetadata())
createItem: (parent, payload) ->
payload.parent_location = parent
payload.parent_locator = parent
$.postJSON(
"/create_item"
@model.urlRoot
payload
(data) =>
@model.set(id: data.id)
@model.set(id: data.locator)
@model.set(old_id: data.id)
@$el.data('id', data.id)
@$el.data('update_url', data.update_url)
@$el.data('locator', data.locator)
@render()
)
render: ->
if @model.id
@$el.load("/preview_component/#{@model.id}", =>
if @model.get('old_id')
@$el.load("/preview_component/#{@model.get('old_id')}", =>
@loadDisplay()
@delegateEvents()
)

View File

@@ -1,15 +1,19 @@
define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views/feedback_notification", "coffee/src/models/module", "coffee/src/views/module_edit"],
($, ui, Backbone, PromptView, NotificationView, ModuleModel, ModuleEditView) ->
define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views/feedback_notification",
"coffee/src/views/module_edit", "js/models/module_info", "js/utils/module"],
($, ui, Backbone, PromptView, NotificationView, ModuleEditView, ModuleModel, ModuleUtils) ->
class TabsEdit extends Backbone.View
initialize: =>
@$('.component').each((idx, element) =>
model = new ModuleModel({
id: $(element).data('locator'),
old_id:$(element).data('id')
})
new ModuleEditView(
el: element,
onDelete: @deleteTab,
model: new ModuleModel(
id: $(element).data('id'),
)
model: model
)
)
@@ -28,7 +32,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
tabMoved: (event, ui) =>
tabs = []
@$('.component').each((idx, element) =>
tabs.push($(element).data('id'))
tabs.push($(element).data('locator'))
)
analytics.track "Reordered Static Pages",
@@ -78,13 +82,13 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
analytics.track "Deleted Static Page",
course: course_location_analytics
id: $component.data('id')
id: $component.data('locator')
deleting = new NotificationView.Mini
title: gettext('Deleting&hellip;')
deleting.show()
$.ajax({
type: 'DELETE',
url: $component.data('update_url')
url: ModuleUtils.getUpdateUrl($component.data('locator'))
}).success(=>
$component.remove()
deleting.hide()

View File

@@ -1,7 +1,7 @@
define ["jquery", "jquery.ui", "gettext", "backbone",
"js/views/feedback_notification", "js/views/feedback_prompt",
"coffee/src/models/module", "coffee/src/views/module_edit"],
($, ui, gettext, Backbone, NotificationView, PromptView, ModuleModel, ModuleEditView) ->
"coffee/src/views/module_edit", "js/models/module_info"],
($, ui, gettext, Backbone, NotificationView, PromptView, ModuleEditView, ModuleModel) ->
class UnitEditView extends Backbone.View
events:
'click .new-component .new-component-type a.multiple-templates': 'showComponentTemplates'
@@ -61,11 +61,13 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
)
@$('.component').each (idx, element) =>
model = new ModuleModel
id: $(element).data('locator')
old_id: $(element).data('id')
new ModuleEditView
el: element,
onDelete: @deleteComponent,
model: new ModuleModel
id: $(element).data('id')
model: model
showComponentTemplates: (event) =>
event.preventDefault()
@@ -96,7 +98,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@$newComponentItem.before(editor.$el)
editor.createItem(
@$el.data('id'),
@$el.data('locator'),
$(event.currentTarget).data()
)
@@ -107,7 +109,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@closeNewComponent(event)
components: => @$('.component').map((idx, el) -> $(el).data('id')).get()
components: => @$('.component').map((idx, el) -> $(el).data('locator')).get()
wait: (value) =>
@$('.unit-body').toggleClass("waiting", value)
@@ -136,13 +138,13 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
$component = $(event.currentTarget).parents('.component')
$.ajax({
type: 'DELETE',
url: $component.data('update_url')
url: @model.urlRoot + "/" + $component.data('locator')
}).success(=>
deleting.hide()
analytics.track "Deleted a Component",
course: course_location_analytics
unit_id: unit_location_analytics
id: $component.data('id')
id: $component.data('locator')
$component.remove()
# b/c we don't vigilantly keep children up to date
@@ -165,7 +167,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@wait(true)
$.ajax({
type: 'DELETE',
url: @$el.data('update_url') + "?" + $.param({recurse: true})
url: @model.urlRoot + "/" + @$el.data('locator') + "?" + $.param({recurse: true})
}).success(=>
analytics.track "Deleted Draft",

View File

@@ -1,6 +1,6 @@
require(["domReady", "jquery", "underscore", "gettext", "js/views/feedback_notification", "js/views/feedback_prompt",
"js/utils/get_date", "jquery.ui", "jquery.leanModal", "jquery.form", "jquery.smoothScroll"],
function(domReady, $, _, gettext, NotificationView, PromptView, DateUtils) {
"js/utils/get_date", "js/utils/module", "jquery.ui", "jquery.leanModal", "jquery.form", "jquery.smoothScroll"],
function(domReady, $, _, gettext, NotificationView, PromptView, DateUtils, ModuleUtils) {
var $body;
var $newComponentItem;
@@ -178,7 +178,7 @@ function saveSubsection() {
$spinner.show();
}
var id = $('.subsection-body').data('id');
var locator = $('.subsection-body').data('locator');
// pull all 'normalized' metadata editable fields on page
var metadata_fields = $('input[data-metadata-name]');
@@ -202,12 +202,11 @@ function saveSubsection() {
});
$.ajax({
url: "/save_item",
type: "POST",
url: ModuleUtils.getUpdateUrl(locator),
type: "PUT",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
'id': id,
'metadata': metadata
}),
success: function() {
@@ -226,12 +225,12 @@ function createNewUnit(e) {
analytics.track('Created a Unit', {
'course': course_location_analytics,
'parent_location': parent
'parent_locator': parent
});
$.postJSON('/create_item', {
'parent_location': parent,
$.postJSON(ModuleUtils.getUpdateUrl(), {
'parent_locator': parent,
'category': category,
'display_name': 'New Unit'
},
@@ -267,11 +266,11 @@ function _deleteItem($el, type) {
click: function(view) {
view.hide();
var id = $el.data('id');
var locator = $el.data('locator');
analytics.track('Deleted an Item', {
'course': course_location_analytics,
'id': id
'id': locator
});
var deleting = new NotificationView.Mini({
@@ -281,7 +280,7 @@ function _deleteItem($el, type) {
$.ajax({
type: 'DELETE',
url: $el.data('update_url')+'?'+ $.param({recurse: true, all_versions: true}),
url: ModuleUtils.getUpdateUrl(locator) +'?'+ $.param({recurse: true, all_versions: true}),
success: function () {
$el.remove();
deleting.hide();

View File

@@ -1,6 +1,6 @@
define(["backbone"], function(Backbone) {
define(["backbone", "js/utils/module"], function(Backbone, ModuleUtils) {
var ModuleInfo = Backbone.Model.extend({
urlRoot: "/xblock",
urlRoot: ModuleUtils.urlRoot,
defaults: {
"id": null,

View File

@@ -1,4 +1,6 @@
define(["backbone", "gettext", "js/views/feedback_notification"], function(Backbone, gettext, NotificationView) {
define(["backbone", "gettext", "js/views/feedback_notification", "js/utils/module"],
function(Backbone, gettext, NotificationView, ModuleUtils) {
var Section = Backbone.Model.extend({
defaults: {
"name": ""
@@ -8,10 +10,9 @@ define(["backbone", "gettext", "js/views/feedback_notification"], function(Backb
return gettext("You must specify a name");
}
},
url: "/save_item",
urlRoot: ModuleUtils.urlRoot,
toJSON: function() {
return {
id: this.get("id"),
metadata: {
display_name: this.get("name")
}

View File

@@ -0,0 +1,17 @@
define(['js/utils/module'],
function (ModuleUtils) {
describe('urlRoot ', function () {
it('defines xblock urlRoot', function () {
expect(ModuleUtils.urlRoot).toBe('/xblock');
});
});
describe('getUpdateUrl ', function () {
it('can take no arguments', function () {
expect(ModuleUtils.getUpdateUrl()).toBe('/xblock');
});
it('appends a locator', function () {
expect(ModuleUtils.getUpdateUrl("locator")).toBe('/xblock/locator');
});
});
}
);

View File

@@ -0,0 +1,26 @@
/**
* Utilities for modules/xblocks.
*
* Returns:
*
* urlRoot: the root for creating/updating an xblock.
* getUpdateUrl: a utility method that returns the xblock update URL, appending
* the location if passed in.
*/
define([], function () {
var urlRoot = '/xblock';
var getUpdateUrl = function (locator) {
if (locator === undefined) {
return urlRoot;
}
else {
return urlRoot + "/" + locator;
}
};
return {
urlRoot: urlRoot,
getUpdateUrl: getUpdateUrl
};
});

View File

@@ -1,6 +1,7 @@
define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/feedback_notification", "draggabilly",
"js/utils/modal", "js/utils/cancel_on_escape", "js/utils/get_date"],
function (domReady, $, ui, _, gettext, NotificationView, Draggabilly, ModalUtils, CancelOnEscape, DateUtils) {
"js/utils/modal", "js/utils/cancel_on_escape", "js/utils/get_date", "js/utils/module"],
function (domReady, $, ui, _, gettext, NotificationView, Draggabilly, ModalUtils, CancelOnEscape,
DateUtils, ModuleUtils) {
var modalSelector = '.edit-subsection-publish-settings';
@@ -37,7 +38,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
var editSectionPublishDate = function (e) {
e.preventDefault();
var $modal = $(modalSelector);
$modal.attr('data-id', $(this).attr('data-id'));
$modal.attr('data-locator', $(this).attr('data-locator'));
$modal.find('.start-date').val($(this).attr('data-date'));
$modal.find('.start-time').val($(this).attr('data-time'));
if ($modal.find('.start-date').val() == '' && $modal.find('.start-time').val() == '') {
@@ -55,11 +56,11 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
$('.edit-subsection-publish-settings .start-time')
);
var id = $(modalSelector).attr('data-id');
var locator = $(modalSelector).attr('data-locator');
analytics.track('Edited Section Release Date', {
'course': course_location_analytics,
'id': id,
'id': locator,
'start': datetime
});
@@ -69,12 +70,11 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
saving.show();
// call into server to commit the new order
$.ajax({
url: "/save_item",
type: "POST",
url: ModuleUtils.getUpdateUrl(locator),
type: "PUT",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
'id': id,
'metadata': {
'start': datetime
}
@@ -86,18 +86,18 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
return (number < 10 ? '0' : '') + number;
};
var $thisSection = $('.courseware-section[data-id="' + id + '"]');
var $thisSection = $('.courseware-section[data-locator="' + locator + '"]');
var html = _.template(
'<span class="published-status">' +
'<strong>' + gettext("Will Release:") + '&nbsp;</strong>' +
gettext("{month}/{day}/{year} at {hour}:{minute} UTC") +
'</span>' +
'<a href="#" class="edit-button" data-date="{month}/{day}/{year}" data-time="{hour}:{minute}" data-id="{id}">' +
'<a href="#" class="edit-button" data-date="{month}/{day}/{year}" data-time="{hour}:{minute}" data-locator="{locator}">' +
gettext("Edit") +
'</a>',
{year: datetime.getUTCFullYear(), month: pad2(datetime.getUTCMonth() + 1), day: pad2(datetime.getUTCDate()),
hour: pad2(datetime.getUTCHours()), minute: pad2(datetime.getUTCMinutes()),
id: id},
locator: locator},
{interpolate: /\{(.+?)\}/g});
$thisSection.find('.section-published-date').html(html);
ModalUtils.hideModal();
@@ -132,14 +132,14 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
'display_name': display_name
});
$.postJSON('/create_item', {
'parent_location': parent,
$.postJSON(ModuleUtils.getUpdateUrl(), {
'parent_locator': parent,
'category': category,
'display_name': display_name
},
function(data) {
if (data.id != undefined) location.reload();
if (data.locator != undefined) location.reload();
});
};
@@ -159,7 +159,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
var $saveButton = $newSubsection.find('.new-subsection-name-save');
var $cancelButton = $newSubsection.find('.new-subsection-name-cancel');
var parent = $(this).parents("section.branch").data("id");
var parent = $(this).parents("section.branch").data("locator");
$saveButton.data('parent', parent);
$saveButton.data('category', $(this).data('category'));
@@ -182,14 +182,14 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
});
$.postJSON('/create_item', {
'parent_location': parent,
$.postJSON(ModuleUtils.getUpdateUrl(), {
'parent_locator': parent,
'category': category,
'display_name': display_name
},
function(data) {
if (data.id != undefined) {
if (data.locator != undefined) {
location.reload();
}
});
@@ -219,7 +219,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
// Exclude the 'new unit' buttons, and make sure we don't
// prepend an element to itself
var siblings = container.children().filter(function () {
return $(this).data('id') !== undefined && !$(this).is(ele);
return $(this).data('locator') !== undefined && !$(this).is(ele);
});
// If the container is collapsed, check to see if the
// element is on top of its parent list -- don't check the
@@ -416,16 +416,16 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
var parentSelector = ele.data('parent-location-selector');
var childrenSelector = ele.data('child-selector');
var newParentEle = ele.parents(parentSelector).first();
var newParentID = newParentEle.data('id');
var oldParentID = ele.data('parent-id');
var newParentLocator = newParentEle.data('locator');
var oldParentLocator = ele.data('parent');
// If the parent has changed, update the children of the old parent.
if (oldParentID !== newParentID) {
if (newParentLocator !== oldParentLocator) {
// Find the old parent element.
var oldParentEle = $(parentSelector).filter(function () {
return $(this).data('id') === oldParentID;
return $(this).data('locator') === oldParentLocator;
});
this.saveItem(oldParentEle, childrenSelector, function () {
ele.data('parent-id', newParentID);
ele.data('parent', newParentLocator);
});
}
var saving = new NotificationView.Mini({
@@ -452,16 +452,15 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
var children = _.map(
ele.find(childrenSelector),
function (child) {
return $(child).data('id');
return $(child).data('locator');
}
);
$.ajax({
url: '/save_item',
type: 'POST',
url: ModuleUtils.getUpdateUrl(ele.data('locator')),
type: 'PUT',
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify({
id: ele.data('id'),
children: children
}),
success: success

View File

@@ -68,7 +68,7 @@ src_paths:
spec_paths:
- coffee/spec/main.js
- coffee/spec
- js_spec
- js/spec
# Paths to fixture files (optional)
# The fixture path will be set automatically when using jasmine-jquery.