Everything tested and ready for Tom to fix
This commit is contained in:
148
cms/djangoapps/contentstore/course_info_model.py
Normal file
148
cms/djangoapps/contentstore/course_info_model.py
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||||
|
from xmodule.modulestore import Location
|
||||||
|
from xmodule.modulestore.django import modulestore
|
||||||
|
from lxml import etree
|
||||||
|
import re
|
||||||
|
from django.http import HttpResponseBadRequest
|
||||||
|
|
||||||
|
## TODO store as array of { date, content } and override course_info_module.definition_from_xml
|
||||||
|
## This should be in a class which inherits from XmlDescriptor
|
||||||
|
def get_course_updates(location):
|
||||||
|
"""
|
||||||
|
Retrieve the relevant course_info updates and unpack into the model which the client expects:
|
||||||
|
[{id : location.url() + idx to make unique, date : string, content : html string}]
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
course_updates = modulestore('direct').get_item(location)
|
||||||
|
except ItemNotFoundError:
|
||||||
|
template = Location(['i4x', 'edx', "templates", 'course_info', "Empty"])
|
||||||
|
course_updates = modulestore('direct').clone_item(template, Location(location))
|
||||||
|
|
||||||
|
# current db rep: {"_id" : locationjson, "definition" : { "data" : "<ol>[<li><h2>date</h2>content</li>]</ol>"} "metadata" : ignored}
|
||||||
|
location_base = course_updates.location.url()
|
||||||
|
|
||||||
|
# purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break.
|
||||||
|
try:
|
||||||
|
course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True))
|
||||||
|
except etree.XMLSyntaxError:
|
||||||
|
course_html_parsed = etree.fromstring("<ol></ol>")
|
||||||
|
|
||||||
|
# Confirm that root is <ol>, iterate over <li>, pull out <h2> subs and then rest of val
|
||||||
|
course_upd_collection = []
|
||||||
|
if course_html_parsed.tag == 'ol':
|
||||||
|
# 0 is the oldest so that new ones get unique idx
|
||||||
|
for idx, update in enumerate(course_html_parsed.iter("li")):
|
||||||
|
if (len(update) == 0):
|
||||||
|
continue
|
||||||
|
elif (len(update) == 1):
|
||||||
|
content = update.find("h2").tail
|
||||||
|
else:
|
||||||
|
content = etree.tostring(update[1])
|
||||||
|
|
||||||
|
course_upd_collection.append({"id" : location_base + "/" + str(idx),
|
||||||
|
"date" : update.findtext("h2"),
|
||||||
|
"content" : content})
|
||||||
|
# return newest to oldest
|
||||||
|
course_upd_collection.reverse()
|
||||||
|
return course_upd_collection
|
||||||
|
|
||||||
|
def update_course_updates(location, update, passed_id=None):
|
||||||
|
"""
|
||||||
|
Either add or update the given course update. It will add it if the passed_id is absent or None. It will update it if
|
||||||
|
it has an passed_id which has a valid value. Until updates have distinct values, the passed_id is the location url + an index
|
||||||
|
into the html structure.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
course_updates = modulestore('direct').get_item(location)
|
||||||
|
except ItemNotFoundError:
|
||||||
|
return HttpResponseBadRequest
|
||||||
|
|
||||||
|
# purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break.
|
||||||
|
try:
|
||||||
|
course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True))
|
||||||
|
except etree.XMLSyntaxError:
|
||||||
|
course_html_parsed = etree.fromstring("<ol></ol>")
|
||||||
|
|
||||||
|
try:
|
||||||
|
new_html_parsed = etree.fromstring(update['content'], etree.XMLParser(remove_blank_text=True))
|
||||||
|
except etree.XMLSyntaxError:
|
||||||
|
new_html_parsed = None
|
||||||
|
|
||||||
|
# Confirm that root is <ol>, iterate over <li>, pull out <h2> subs and then rest of val
|
||||||
|
if course_html_parsed.tag == 'ol':
|
||||||
|
# ??? Should this use the id in the json or in the url or does it matter?
|
||||||
|
if passed_id:
|
||||||
|
element = course_html_parsed.findall("li")[get_idx(passed_id)]
|
||||||
|
element[0].text = update['date']
|
||||||
|
if (len(element) == 1):
|
||||||
|
if new_html_parsed is not None:
|
||||||
|
element[0].tail = None
|
||||||
|
element.append(new_html_parsed)
|
||||||
|
else:
|
||||||
|
element[0].tail = update['content']
|
||||||
|
else:
|
||||||
|
if new_html_parsed is not None:
|
||||||
|
element[1] = new_html_parsed
|
||||||
|
else:
|
||||||
|
element.pop(1)
|
||||||
|
element[0].tail = update['content']
|
||||||
|
else:
|
||||||
|
idx = len(course_html_parsed.findall("li"))
|
||||||
|
passed_id = course_updates.location.url() + "/" + str(idx)
|
||||||
|
element = etree.SubElement(course_html_parsed, "li")
|
||||||
|
date_element = etree.SubElement(element, "h2")
|
||||||
|
date_element.text = update['date']
|
||||||
|
if new_html_parsed is not None:
|
||||||
|
element[1] = new_html_parsed
|
||||||
|
else:
|
||||||
|
date_element.tail = update['content']
|
||||||
|
|
||||||
|
# update db record
|
||||||
|
course_updates.definition['data'] = etree.tostring(course_html_parsed)
|
||||||
|
modulestore('direct').update_item(location, course_updates.definition['data'])
|
||||||
|
|
||||||
|
return {"id" : passed_id,
|
||||||
|
"date" : update['date'],
|
||||||
|
"content" :update['content']}
|
||||||
|
|
||||||
|
def delete_course_update(location, update, passed_id):
|
||||||
|
"""
|
||||||
|
Delete the given course_info update from the db.
|
||||||
|
Returns the resulting course_updates b/c their ids change.
|
||||||
|
"""
|
||||||
|
if not passed_id:
|
||||||
|
return HttpResponseBadRequest
|
||||||
|
|
||||||
|
try:
|
||||||
|
course_updates = modulestore('direct').get_item(location)
|
||||||
|
except ItemNotFoundError:
|
||||||
|
return HttpResponseBadRequest
|
||||||
|
|
||||||
|
# TODO use delete_blank_text parser throughout and cache as a static var in a class
|
||||||
|
# purely to handle free formed updates not done via editor. Actually kills them, but at least doesn't break.
|
||||||
|
try:
|
||||||
|
course_html_parsed = etree.fromstring(course_updates.definition['data'], etree.XMLParser(remove_blank_text=True))
|
||||||
|
except etree.XMLSyntaxError:
|
||||||
|
course_html_parsed = etree.fromstring("<ol></ol>")
|
||||||
|
|
||||||
|
if course_html_parsed.tag == 'ol':
|
||||||
|
# ??? Should this use the id in the json or in the url or does it matter?
|
||||||
|
element_to_delete = course_html_parsed.xpath('/ol/li[position()=' + str(get_idx(passed_id) + 1) + "]")
|
||||||
|
if element_to_delete:
|
||||||
|
course_html_parsed.remove(element_to_delete[0])
|
||||||
|
|
||||||
|
# update db record
|
||||||
|
course_updates.definition['data'] = etree.tostring(course_html_parsed)
|
||||||
|
store = modulestore('direct')
|
||||||
|
store.update_item(location, course_updates.definition['data'])
|
||||||
|
|
||||||
|
return get_course_updates(location)
|
||||||
|
|
||||||
|
def get_idx(passed_id):
|
||||||
|
"""
|
||||||
|
From the url w/ idx appended, get the idx.
|
||||||
|
"""
|
||||||
|
# TODO compile this regex into a class static and reuse for each call
|
||||||
|
idx_matcher = re.search(r'.*/(\d)+$', passed_id)
|
||||||
|
if idx_matcher:
|
||||||
|
return int(idx_matcher.group(1))
|
||||||
@@ -3,6 +3,6 @@
|
|||||||
"/static/js/vendor/jquery.min.js",
|
"/static/js/vendor/jquery.min.js",
|
||||||
"/static/js/vendor/json2.js",
|
"/static/js/vendor/json2.js",
|
||||||
"/static/js/vendor/underscore-min.js",
|
"/static/js/vendor/underscore-min.js",
|
||||||
"/static/js/vendor/backbone-min.js"
|
"/static/js/vendor/backbone.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<li>
|
||||||
|
<!-- FIXME what style should we use for initially hidden? --> <!-- TODO decide whether this should use codemirror -->
|
||||||
|
<form class="new-update-form">
|
||||||
|
<div class="row">
|
||||||
|
<label class="inline-label">Date:</label>
|
||||||
|
<!-- TODO replace w/ date widget and actual date (problem is that persisted version is "Month day" not an actual date obj -->
|
||||||
|
<input type="text" id="date-entry"
|
||||||
|
value="<%= updateModel.get('date') %>"></input>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<textarea class="new-update-content text-editor"><%= updateModel.get('content') %></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<!-- cid rather than id b/c new ones have cid's not id's -->
|
||||||
|
<a href="#" class="save-button" name="<%= updateModel.cid %>">Save</a>
|
||||||
|
<a href="#" class="cancel-button" name="<%= updateModel.cid %>">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<h2>
|
||||||
|
<span class="calendar-icon"></span><span id="date-display"><%=
|
||||||
|
updateModel.get('date') %></span>
|
||||||
|
</h2>
|
||||||
|
<div class="update-contents"><%= updateModel.get('content') %></div>
|
||||||
|
<div class="row">
|
||||||
|
<a href="#" class="edit-button" name="<%- updateModel.cid %>">Edit</a>
|
||||||
|
<a href="#" class="delete-button" name="<%- updateModel.cid %>"">Delete</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
14
cms/static/coffee/src/client_templates/load_templates.html
Normal file
14
cms/static/coffee/src/client_templates/load_templates.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<!-- In order to enable better debugging of templates, put them in
|
||||||
|
the script tag section.
|
||||||
|
TODO add lazy load fn to load templates as needed (called
|
||||||
|
from backbone view initialize to set this.template of the view)
|
||||||
|
-->
|
||||||
|
|
||||||
|
<%block name="jsextra">
|
||||||
|
<script type="text/javascript" charset="utf-8">
|
||||||
|
// How do I load an html file server side so I can
|
||||||
|
// Precompiling your templates can be a big help when debugging errors you can't reproduce. This is because precompiled templates can provide line numbers and a stack trace, something that is not possible when compiling templates on the client. The source property is available on the compiled template function for easy precompilation.
|
||||||
|
// <script>CMS.course_info_update = <%= _.template(jstText).source %>;</script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</%block>
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
## Derived from and should inherit from a common ancestor w/ ModuleEdit
|
|
||||||
class CMS.Views.CourseInfoEdit extends Backbone.View
|
|
||||||
tagName: 'div'
|
|
||||||
className: 'component'
|
|
||||||
|
|
||||||
events:
|
|
||||||
"click .component-editor .cancel-button": 'clickCancelButton'
|
|
||||||
"click .component-editor .save-button": 'clickSaveButton'
|
|
||||||
"click .component-actions .edit-button": 'clickEditButton'
|
|
||||||
"click .component-actions .delete-button": 'onDelete'
|
|
||||||
|
|
||||||
initialize: ->
|
|
||||||
@render()
|
|
||||||
|
|
||||||
$component_editor: => @$el.find('.component-editor')
|
|
||||||
|
|
||||||
loadDisplay: ->
|
|
||||||
XModule.loadModule(@$el.find('.xmodule_display'))
|
|
||||||
|
|
||||||
loadEdit: ->
|
|
||||||
if not @module
|
|
||||||
@module = XModule.loadModule(@$el.find('.xmodule_edit'))
|
|
||||||
|
|
||||||
# don't show metadata (deprecated for course_info)
|
|
||||||
render: ->
|
|
||||||
if @model.id
|
|
||||||
@$el.load("/preview_component/#{@model.id}", =>
|
|
||||||
@loadDisplay()
|
|
||||||
@delegateEvents()
|
|
||||||
)
|
|
||||||
|
|
||||||
clickSaveButton: (event) =>
|
|
||||||
event.preventDefault()
|
|
||||||
data = @module.save()
|
|
||||||
@model.save(data).done( =>
|
|
||||||
# # showToastMessage("Your changes have been saved.", null, 3)
|
|
||||||
@module = null
|
|
||||||
@render()
|
|
||||||
@$el.removeClass('editing')
|
|
||||||
).fail( ->
|
|
||||||
showToastMessage("There was an error saving your changes. Please try again.", null, 3)
|
|
||||||
)
|
|
||||||
|
|
||||||
clickCancelButton: (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
@$el.removeClass('editing')
|
|
||||||
@$component_editor().slideUp(150)
|
|
||||||
|
|
||||||
clickEditButton: (event) ->
|
|
||||||
event.preventDefault()
|
|
||||||
@$el.addClass('editing')
|
|
||||||
@$component_editor().slideDown(150)
|
|
||||||
@loadEdit()
|
|
||||||
|
|
||||||
onDelete: (event) ->
|
|
||||||
# clear contents, don't delete
|
|
||||||
@model.definition.data = "<ol></ol>"
|
|
||||||
# TODO change label to 'clear'
|
|
||||||
|
|
||||||
onNew: (event) ->
|
|
||||||
ele = $(@model.definition.data).find("ol")
|
|
||||||
if (ele)
|
|
||||||
ele = $(ele).first().prepend("<li><h2>" + $.datepicker.formatDate('MM d', new Date()) + "</h2>/n</li>");
|
|
||||||
34
cms/static/js/models/course_info.js
Normal file
34
cms/static/js/models/course_info.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// single per course holds the updates and handouts
|
||||||
|
CMS.Models.CourseInfo = Backbone.Model.extend({
|
||||||
|
// This model class is not suited for restful operations and is considered just a server side initialized container
|
||||||
|
url: '',
|
||||||
|
|
||||||
|
defaults: {
|
||||||
|
"courseId": "", // the location url
|
||||||
|
"updates" : null, // UpdateCollection
|
||||||
|
"handouts": null // HandoutCollection
|
||||||
|
},
|
||||||
|
|
||||||
|
idAttribute : "courseId"
|
||||||
|
});
|
||||||
|
|
||||||
|
// course update -- biggest kludge here is the lack of a real id to map updates to originals
|
||||||
|
CMS.Models.CourseUpdate = Backbone.Model.extend({
|
||||||
|
defaults: {
|
||||||
|
"date" : $.datepicker.formatDate('MM d', new Date()),
|
||||||
|
"content" : ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
The intitializer of this collection must set id to the update's location.url and courseLocation to the course's location. Must pass the
|
||||||
|
collection of updates as [{ date : "month day", content : "html"}]
|
||||||
|
*/
|
||||||
|
CMS.Models.CourseUpdateCollection = Backbone.Collection.extend({
|
||||||
|
url : function() {return this.urlbase + "course_info/updates/";},
|
||||||
|
|
||||||
|
model : CMS.Models.CourseUpdate
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
77
cms/static/js/template_loader.js
Normal file
77
cms/static/js/template_loader.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// <!-- from https://github.com/Gazler/Underscore-Template-Loader/blob/master/index.html -->
|
||||||
|
// TODO Figure out how to initialize w/ static views from server (don't call .load but instead inject in django as strings)
|
||||||
|
// so this only loads the lazily loaded ones.
|
||||||
|
(function() {
|
||||||
|
if (typeof window.templateLoader == 'function') return;
|
||||||
|
|
||||||
|
var templateLoader = {
|
||||||
|
templateVersion: "0.0.3",
|
||||||
|
templates: {},
|
||||||
|
loadRemoteTemplate: function(templateName, filename, callback) {
|
||||||
|
if (!this.templates[templateName]) {
|
||||||
|
var self = this;
|
||||||
|
jQuery.ajax({url : filename,
|
||||||
|
success : function(data) {
|
||||||
|
self.addTemplate(templateName, data);
|
||||||
|
self.saveLocalTemplates();
|
||||||
|
callback(data);
|
||||||
|
},
|
||||||
|
error : function(xhdr, textStatus, errorThrown) {
|
||||||
|
console.log(textStatus); },
|
||||||
|
dataType : "html"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
callback(this.templates[templateName]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
addTemplate: function(templateName, data) {
|
||||||
|
// is there a reason this doesn't go ahead and compile the template? _.template(data)
|
||||||
|
// I suppose localstorage use would still req raw string rather than compiled version, but that sd work
|
||||||
|
// if it maintains a separate cache of uncompiled ones
|
||||||
|
this.templates[templateName] = data;
|
||||||
|
},
|
||||||
|
|
||||||
|
localStorageAvailable: function() {
|
||||||
|
try {
|
||||||
|
return 'localStorage' in window && window['localStorage'] !== null;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveLocalTemplates: function() {
|
||||||
|
if (this.localStorageAvailable) {
|
||||||
|
localStorage.setItem("templates", JSON.stringify(this.templates));
|
||||||
|
localStorage.setItem("templateVersion", this.templateVersion);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
loadLocalTemplates: function() {
|
||||||
|
if (this.localStorageAvailable) {
|
||||||
|
var templateVersion = localStorage.getItem("templateVersion");
|
||||||
|
if (templateVersion && templateVersion == this.templateVersion) {
|
||||||
|
var templates = localStorage.getItem("templates");
|
||||||
|
if (templates) {
|
||||||
|
templates = JSON.parse(templates);
|
||||||
|
for (var x in templates) {
|
||||||
|
if (!this.templates[x]) {
|
||||||
|
this.addTemplate(x, templates[x]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
localStorage.removeItem("templates");
|
||||||
|
localStorage.removeItem("templateVersion");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
templateLoader.loadLocalTemplates();
|
||||||
|
window.templateLoader = templateLoader;
|
||||||
|
})();
|
||||||
138
cms/static/js/views/course_info_edit.js
Normal file
138
cms/static/js/views/course_info_edit.js
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/* this view should own everything on the page which has controls effecting its operation
|
||||||
|
generate other views for the individual editors.
|
||||||
|
The render here adds views for each update/handout by delegating to their collections but does not
|
||||||
|
generate any html for the surrounding page.
|
||||||
|
*/
|
||||||
|
CMS.Views.CourseInfoEdit = Backbone.View.extend({
|
||||||
|
// takes CMS.Models.CourseInfo as model
|
||||||
|
tagName: 'div',
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
// instantiate the ClassInfoUpdateView and delegate the proper dom to it
|
||||||
|
new CMS.Views.ClassInfoUpdateView({
|
||||||
|
el: this.$('#course-update-view'),
|
||||||
|
collection: this.model.get('updates')
|
||||||
|
});
|
||||||
|
// TODO instantiate the handouts view
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ??? Programming style question: should each of these classes be in separate files?
|
||||||
|
CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
|
||||||
|
// collection is CourseUpdateCollection
|
||||||
|
events: {
|
||||||
|
"click .new-update-button" : "onNew",
|
||||||
|
"click .save-button" : "onSave",
|
||||||
|
"click .cancel-button" : "onCancel",
|
||||||
|
"click .edit-button" : "onEdit",
|
||||||
|
"click .delete-button" : "onDelete"
|
||||||
|
},
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
var self = this;
|
||||||
|
// instantiates an editor template for each update in the collection
|
||||||
|
window.templateLoader.loadRemoteTemplate("course_info_update",
|
||||||
|
// TODO Where should the template reside? how to use the static.url to create the path?
|
||||||
|
"/static/coffee/src/client_templates/course_info_update.html",
|
||||||
|
function (raw_template) {
|
||||||
|
self.template = _.template(raw_template);
|
||||||
|
self.render();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function () {
|
||||||
|
// iterate over updates and create views for each using the template
|
||||||
|
var updateEle = this.$el.find("#course-update-list");
|
||||||
|
// remove and then add all children
|
||||||
|
$(updateEle).empty();
|
||||||
|
var self = this;
|
||||||
|
this.collection.each(function (update) {
|
||||||
|
var newEle = self.template({ updateModel : update });
|
||||||
|
$(updateEle).append(newEle);
|
||||||
|
});
|
||||||
|
this.$el.find(".new-update-form").hide();
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
|
||||||
|
onNew: function(event) {
|
||||||
|
// create new obj, insert into collection, and render this one ele overriding the hidden attr
|
||||||
|
var newModel = new CMS.Models.CourseUpdate();
|
||||||
|
this.collection.add(newModel, {at : 0});
|
||||||
|
|
||||||
|
var newForm = this.template({ updateModel : newModel });
|
||||||
|
var updateEle = this.$el.find("#course-update-list");
|
||||||
|
$(updateEle).append(newForm);
|
||||||
|
$(newForm).find(".new-update-form").show();
|
||||||
|
},
|
||||||
|
|
||||||
|
onSave: function(event) {
|
||||||
|
var targetModel = this.eventModel(event);
|
||||||
|
targetModel.set({ date : this.dateEntry(event).val(), content : this.contentEntry(event).val() });
|
||||||
|
// push change to display, hide the editor, submit the change
|
||||||
|
$(this.dateDisplay(event)).val(targetModel.get('date'));
|
||||||
|
$(this.contentDisplay(event)).val(targetModel.get('content'));
|
||||||
|
$(this.editor(event)).hide();
|
||||||
|
|
||||||
|
targetModel.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
onCancel: function(event) {
|
||||||
|
// change editor contents back to model values and hide the editor
|
||||||
|
$(this.editor(event)).hide();
|
||||||
|
var targetModel = this.eventModel(event);
|
||||||
|
$(this.dateEntry(event)).val(targetModel.get('date'));
|
||||||
|
$(this.contentEntry(event)).val(targetModel.get('content'));
|
||||||
|
},
|
||||||
|
|
||||||
|
onEdit: function(event) {
|
||||||
|
$(this.editor(event)).show();
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete: function(event) {
|
||||||
|
// TODO ask for confirmation
|
||||||
|
// remove the dom element and delete the model
|
||||||
|
var targetModel = this.eventModel(event);
|
||||||
|
this.modelDom(event).remove();
|
||||||
|
var cacheThis = this;
|
||||||
|
targetModel.destroy({success : function (model, response) {
|
||||||
|
cacheThis.collection.fetch({success : function() {cacheThis.render();}});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Dereferencing from events to screen elements
|
||||||
|
eventModel: function(event) {
|
||||||
|
// not sure if it should be currentTarget or delegateTarget
|
||||||
|
return this.collection.getByCid($(event.currentTarget).attr("name"));
|
||||||
|
},
|
||||||
|
|
||||||
|
modelDom: function(event) {
|
||||||
|
return $(event.currentTarget).closest("li");
|
||||||
|
},
|
||||||
|
|
||||||
|
editor: function(event) {
|
||||||
|
var li = $(event.currentTarget).closest("li");
|
||||||
|
if (li) return $(li).find("form").first();
|
||||||
|
},
|
||||||
|
|
||||||
|
dateEntry: function(event) {
|
||||||
|
var li = $(event.currentTarget).closest("li");
|
||||||
|
if (li) return $(li).find("#date-entry").first();
|
||||||
|
},
|
||||||
|
|
||||||
|
contentEntry: function(event) {
|
||||||
|
return $(event.currentTarget).closest("li").find(".new-update-content").first();
|
||||||
|
},
|
||||||
|
|
||||||
|
dateDisplay: function(event) {
|
||||||
|
return $(event.currentTarget).closest("li").find("#date-display").first();
|
||||||
|
},
|
||||||
|
|
||||||
|
contentDisplay: function(event) {
|
||||||
|
return $(event.currentTarget).closest("li").find(".update-contents").first();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
@@ -1,16 +1,31 @@
|
|||||||
<%inherit file="base.html" />
|
<%inherit file="base.html" />
|
||||||
|
<%namespace name='static' file='static_content.html'/>
|
||||||
|
|
||||||
<!-- TODO decode course # from context_course into title -->
|
<!-- TODO decode course # from context_course into title -->
|
||||||
<%block name="title">Course Info</%block>
|
<%block name="title">Course Info</%block>
|
||||||
|
|
||||||
<%block name="jsextra">
|
<%block name="jsextra">
|
||||||
|
<script type="text/javascript" src="${static.url('js/template_loader.js')}"></script>
|
||||||
|
<script type="text/javascript" src="${static.url('js/models/course_info.js')}"></script>
|
||||||
|
<script type="text/javascript" src="${static.url('js/views/course_info_edit.js')}"></script>
|
||||||
|
|
||||||
<script type="text/javascript" charset="utf-8">
|
<script type="text/javascript" charset="utf-8">
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
editor = new CMS.Views.CourseInfoEdit({
|
|
||||||
el: $('.course-updates'),
|
var course_updates = new CMS.Models.CourseUpdateCollection();
|
||||||
model : new CMS.Models.Module({id : '${course_updates.location.url()}'})
|
course_updates.reset(${course_updates|n});
|
||||||
});
|
course_updates.urlbase = '${url_base}';
|
||||||
$(".new-update-button").bind('click', editor.onNew);
|
|
||||||
});
|
var editor = new CMS.Views.CourseInfoEdit({
|
||||||
|
el: $('.main-wrapper'),
|
||||||
|
model : new CMS.Models.CourseInfo({
|
||||||
|
courseId : '${context_course.location}',
|
||||||
|
updates : course_updates,
|
||||||
|
// FIXME add handouts
|
||||||
|
handouts : null})
|
||||||
|
});
|
||||||
|
editor.render();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</%block>
|
</%block>
|
||||||
|
|
||||||
@@ -19,10 +34,10 @@ $(document).ready(function(){
|
|||||||
<div class="inner-wrapper">
|
<div class="inner-wrapper">
|
||||||
<h1>Course Info</h1>
|
<h1>Course Info</h1>
|
||||||
<div class="main-column">
|
<div class="main-column">
|
||||||
<div class="window">
|
<div class="unit-body window" id="course-update-view">
|
||||||
<h2>Updates</h2>
|
<h2>Updates</h2>
|
||||||
<a href="#" class="new-update-button">New Update</a>
|
<a href="#" class="new-update-button">New Update</a>
|
||||||
<div class="course-updates"></div>
|
<ol class="update-list" id="course-update-list"></ol>
|
||||||
<!-- probably replace w/ a vertical where each element of the vertical is a separate update w/ a date and html field -->
|
<!-- probably replace w/ a vertical where each element of the vertical is a separate update w/ a date and html field -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
metadata:
|
metadata:
|
||||||
display_name: Empty
|
display_name: Empty
|
||||||
data: "<p>This is where you can add additional information about your course.</p>"
|
data: "<ol></ol>"
|
||||||
children: []
|
children: []
|
||||||
Reference in New Issue
Block a user