From f6fb64f130d60cadab19c1dae5d9aadc7a2ecf85 Mon Sep 17 00:00:00 2001 From: cahrens Date: Mon, 18 Mar 2013 15:03:54 -0400 Subject: [PATCH] Saving checkbox state, client template for individual checklist. --- cms/djangoapps/contentstore/views.py | 63 +++++--- cms/static/client_templates/checklist.html | 50 ++++++ cms/static/js/base.js | 4 - cms/static/js/models/checklists.js | 17 ++- cms/static/js/template_loader.js | 143 +++++++++--------- cms/static/js/views/checklists_view.js | 85 ++++++++--- cms/templates/checklists.html | 53 ++----- cms/urls.py | 2 + .../xmodule/templates/course/empty.yaml | 30 ++-- 9 files changed, 263 insertions(+), 184 deletions(-) create mode 100644 cms/static/client_templates/checklist.html diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 89360c1d42..9398952de0 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -1103,11 +1103,7 @@ def course_info_updates(request, org, course, provided_id=None): if not has_access(request.user, location): raise PermissionDenied() - # NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! - if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: - real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'] - else: - real_method = request.method + real_method = get_request_method(request) if request.method == 'GET': return HttpResponse(json.dumps(get_course_updates(location)), mimetype="application/json") @@ -1130,11 +1126,7 @@ def module_info(request, module_location): if not has_access(request.user, location): raise PermissionDenied() - # NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! - if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: - real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'] - else: - real_method = request.method + real_method = get_request_method(request) rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true'] logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links', 'False'), rewrite_static_links)) @@ -1259,21 +1251,18 @@ def course_grader_updates(request, org, course, name, grader_index=None): location = get_location_and_verify_access(request, org, course, name) - if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: - real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'] - else: - real_method = request.method + real_method = get_request_method(request) if real_method == 'GET': # Cannot just do a get w/o knowing the course name :-( - return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(['i4x', org, course, 'course', name]), grader_index)), + return HttpResponse(json.dumps(CourseGradingModel.fetch_grader(Location(location), grader_index)), mimetype="application/json") elif real_method == "DELETE": - # ??? Shoudl this return anything? Perhaps success fail? - CourseGradingModel.delete_grader(Location(['i4x', org, course, 'course', name]), grader_index) + # ??? Should this return anything? Perhaps success fail? + CourseGradingModel.delete_grader(Location(location), grader_index) return HttpResponse() elif request.method == 'POST': # post or put, doesn't matter. - return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(['i4x', org, course, 'course', name]), request.POST)), + return HttpResponse(json.dumps(CourseGradingModel.update_grader_from_json(Location(location), request.POST)), mimetype="application/json") @@ -1289,11 +1278,7 @@ def course_advanced_updates(request, org, course, name): """ location = get_location_and_verify_access(request, org, course, name) - # NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! - if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: - real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'] - else: - real_method = request.method + real_method = get_request_method(request) if real_method == 'GET': return HttpResponse(json.dumps(CourseMetadata.fetch(location)), mimetype="application/json") @@ -1320,13 +1305,32 @@ def get_checklists(request, org, course, name): course_module.metadata[key] = template_module.metadata[key] modulestore.update_metadata(location, course_module.metadata) - + checklists = course_module.metadata[key] return render_to_response('checklists.html', { 'context_course': course_module, - 'checklists' : course_module.metadata[key] + 'checklists' : checklists, + 'checklists_json' : json.dumps(checklists) + }) +@login_required +def update_checklist(request, org, course, name, checklist_index=None): + location = get_location_and_verify_access(request, org, course, name) + modulestore = get_modulestore(location) + course_module = modulestore.get_item(location) + key = "checklists" + + real_method = get_request_method(request) + if checklist_index is not None and (real_method == 'POST' or real_method == 'PUT'): + modified_checklist = json.loads(request.body) + (course_module.metadata[key])[int(checklist_index)] = modified_checklist + modulestore.update_metadata(location, course_module.metadata) + return HttpResponse(json.dumps(modified_checklist), mimetype="application/json") + elif request.method == 'GET': + # TODO: Would we ever get in this condition? Any point in having this code? + return HttpResponse(json.dumps(course_module.metadata[key]), mimetype="application/json") + @login_required @ensure_csrf_cookie @@ -1593,3 +1597,12 @@ def get_location_and_verify_access(request, org, course, name): raise PermissionDenied() return location + +def get_request_method(request): + # NB: we're setting Backbone.emulateHTTP to true on the client so everything comes as a post!!! + if request.method == 'POST' and 'HTTP_X_HTTP_METHOD_OVERRIDE' in request.META: + real_method = request.META['HTTP_X_HTTP_METHOD_OVERRIDE'] + else: + real_method = request.method + + return real_method diff --git a/cms/static/client_templates/checklist.html b/cms/static/client_templates/checklist.html new file mode 100644 index 0000000000..d3e6d9d820 --- /dev/null +++ b/cms/static/client_templates/checklist.html @@ -0,0 +1,50 @@ +
+ <% var widthPercentage = 'width:' + percentChecked + '%;'; %> + + <%= percentChecked %>% of checklist completed +
+

+ + <%= checklistShortDescription %>

+ + Tasks Completed: <%= itemsChecked %>/<%= items.length %> + + +
+ + +
\ No newline at end of file diff --git a/cms/static/js/base.js b/cms/static/js/base.js index dd55b0c5a1..18c31fc4e0 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -14,10 +14,6 @@ $(document).ready(function () { // scopes (namely the course-info tab) window.$modalCover = $modalCover; - // Control whether template caching in local memory occurs (see template_loader.js). Caching screws up development but may - // be a good optimization in production (it works fairly well) - window.cachetemplates = false; - $body.append($modalCover); $newComponentItem = $('.new-component-item'); $newComponentTypePicker = $('.new-component'); diff --git a/cms/static/js/models/checklists.js b/cms/static/js/models/checklists.js index 368db944ed..422c517376 100644 --- a/cms/static/js/models/checklists.js +++ b/cms/static/js/models/checklists.js @@ -1 +1,16 @@ -if (!CMS.Models['Checklists']) CMS.Models.Checklists = new Object(); \ No newline at end of file +CMS.Models.Checklist = Backbone.Model.extend({ +}); + +CMS.Models.ChecklistCollection = Backbone.Collection.extend({ + model : CMS.Models.Checklist, + + parse: function(response) { + _.each(response, + function( element, idx ) { + element.id = idx; + }); + + return response; + } +}); + diff --git a/cms/static/js/template_loader.js b/cms/static/js/template_loader.js index 3492ca677a..45161b3c28 100644 --- a/cms/static/js/template_loader.js +++ b/cms/static/js/template_loader.js @@ -1,78 +1,79 @@ // // 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.15", - 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 { - // window.cachetemplates is global set in base.js to turn caching on/off - return window.cachetemplates && '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"); - } - } - } +(function () { + if (typeof window.templateLoader == 'function') return; + + var templateLoader = { + templateVersion: "0.0.15", + templates: {}, + // Control whether template caching in local memory occurs. Caching screws up development but may + // be a good optimization in production (it works fairly well). + cacheTemplates: false, + 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 this.cacheTemplates && '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; - })(); +})(); diff --git a/cms/static/js/views/checklists_view.js b/cms/static/js/views/checklists_view.js index 1ed79c8fac..06fcd2ee87 100644 --- a/cms/static/js/views/checklists_view.js +++ b/cms/static/js/views/checklists_view.js @@ -5,15 +5,52 @@ CMS.Views.Checklists = Backbone.View.extend({ events : { 'click .course-checklist .checklist-title' : "toggleChecklist", - 'click .course-checklist .task label' : "toggleTask", - 'click .demo-checklistviz' : "demoUpdateProgress" + 'click .course-checklist .task input' : "toggleTask" }, initialize : function() { - // adding class and title needs to happen in HTML -// $('.course-checklist .checklist-title').each(function(e){ -// $(this).addClass('is-selectable').attr('title','Collapse/Expand this Checklist').bind('click', this.toggleChecklist); -// }); + var self = this; + // instantiates an editor template for each update in the collection + window.templateLoader.loadRemoteTemplate("checklist", + "/static/client_templates/checklist.html", + function (raw_template) { + self.template = _.template(raw_template); + self.render(); + } + ); + }, + + render: function() { + // catch potential outside call before template loaded + if (!this.template) return this; + + this.$el.empty(); + + var self = this; + _.each(this.collection.models, + function(checklist, index) { + self.$el.append(self.renderTemplate(checklist, index)); + }); + + return this; + }, + + renderTemplate: function (checklist, index) { + var checklistItems = checklist.attributes['items']; + var itemsChecked = 0; + _.each(checklistItems, + function(checklist) { + if (checklist['is_checked']) { + itemsChecked +=1; + } + }); + var percentChecked = Math.round((itemsChecked/checklistItems.length)*100); + return this.template({ + checklistIndex : index, + checklistShortDescription : checklist.attributes['short_description'], + items: checklistItems, + itemsChecked: itemsChecked, + percentChecked: percentChecked}); }, toggleChecklist : function(e) { @@ -22,27 +59,25 @@ CMS.Views.Checklists = Backbone.View.extend({ }, toggleTask : function (e) { - $(e.target).closest('.task').toggleClass('is-completed'); - }, + var self = this; - // TODO: remove - demoUpdateProgress : function(e) { - (e).preventDefault(); - $('#course-checklist0 .viz-checklist-status .viz-checklist-status-value').css('width','25%'); - }, + var completed = 'is-completed'; + var $checkbox = $(e.target); + var $task = $checkbox.closest('.task'); + $task.toggleClass(completed); - // TODO: not used. In-progress update checklist progress (based on checkbox check/uncheck events) - updateChecklistProgress : function(e) { - var $statusCount = this.$el.closest('.course-checklist').find('.status-count'); - var $statusAmount = this.$el.closest('.course-checklist').find('.status-amount'); - - if (this.$el.attr('checked')) { - console.log('adding'); - } - - else { - console.log('subtracting'); - } + var checklist_index = $checkbox.data('checklist'); + var task_index = $checkbox.data('task'); + var model = this.collection.at(checklist_index); + model.attributes.items[task_index].is_checked = $task.hasClass(completed); + model.save({}, + { + success : function() { + var updatedTemplate = self.renderTemplate(model, checklist_index); + self.$el.find('#course-checklist'+checklist_index).first().replaceWith(updatedTemplate); + }, + error : CMS.ServerError + }); } }); \ No newline at end of file diff --git a/cms/templates/checklists.html b/cms/templates/checklists.html index de44a29862..254d86106c 100644 --- a/cms/templates/checklists.html +++ b/cms/templates/checklists.html @@ -1,21 +1,28 @@ <%inherit file="base.html" /> +<%! from django.core.urlresolvers import reverse %> <%block name="title">Course Checklists <%block name="bodyclass">is-signedin course uxdesign checklists <%namespace name='static' file='static_content.html'/> <%block name="jsextra"> + + +