From 9af447de9721cb7693a23321e2a98e91bed43c34 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Wed, 12 Jun 2013 13:40:40 -0400 Subject: [PATCH] Moved Backbone models/views out to separate files for pdf textbooks project --- cms/static/js/models/textbooks.js | 101 +++++++ cms/static/js/views/textbooks.js | 333 +++++++++++++++++++++++ cms/templates/textbooks.html | 438 +----------------------------- 3 files changed, 437 insertions(+), 435 deletions(-) create mode 100644 cms/static/js/models/textbooks.js create mode 100644 cms/static/js/views/textbooks.js diff --git a/cms/static/js/models/textbooks.js b/cms/static/js/models/textbooks.js new file mode 100644 index 0000000000..baca1a5b3d --- /dev/null +++ b/cms/static/js/models/textbooks.js @@ -0,0 +1,101 @@ +CMS.Models.Textbook = Backbone.AssociatedModel.extend({ + defaults: function() { + return { + name: "", + chapters: new CMS.Collections.ChapterSet([{}]), + showChapters: false + }; + }, + relations: [{ + type: Backbone.Many, + key: "chapters", + relatedModel: "CMS.Models.Chapter", + collectionType: "CMS.Collections.ChapterSet" + }], + isEmpty: function() { + return !this.get('name') && this.get('chapters').isEmpty(); + }, + parse: function(response) { + if("tab_title" in response && !("name" in response)) { + response.name = response.tab_title; + delete response.tab_title; + } + if("url" in response && !("chapters" in response)) { + response.chapters = {"url": response.url}; + delete response.url; + } + _.each(response.chapters, function(chapter, i) { + chapter.order = chapter.order || i+1; + }); + return response; + }, + toJSON: function() { + return { + tab_title: this.get('name'), + chapters: this.get('chapters').toJSON() + }; + } +}); +CMS.Collections.TextbookSet = Backbone.Collection.extend({ + model: CMS.Models.Textbook, + url: function() { return window.TEXTBOOK_URL; }, + initialize: function() { + this.listenTo(this, "editOne", this.editOne); + }, + editOne: function(textbook) { + this.editing = textbook; + }, + save: function(options) { + return this.sync('update', this, options); + } +}); +CMS.Models.Chapter = Backbone.AssociatedModel.extend({ + defaults: function() { + return { + name: "", + asset_path: "", + order: this.collection ? this.collection.nextOrder() : 1 + }; + }, + isEmpty: function() { + return !this.get('name') && !this.get('asset_path'); + }, + parse: function(response) { + if("title" in response && !("name" in response)) { + response.name = response.title; + delete response.title; + } + if("url" in response && !("asset_path" in response)) { + response.asset_path = response.url; + delete response.url; + } + return response; + }, + toJSON: function() { + return { + title: this.get('name'), + url: this.get('asset_path') + }; + } +}); +CMS.Collections.ChapterSet = Backbone.Collection.extend({ + model: CMS.Models.Chapter, + comparator: "order", + nextOrder: function() { + if(!this.length) return 1; + return this.last().get('order') + 1; + }, + isEmpty: function() { + return this.length === 0 || this.every(function(m) { return m.isEmpty(); }); + } +}); +CMS.Models.FileUpload = Backbone.Model.extend({ + defaults: { + "title": "", + "message": "", + "selectFile": null, + "uploading": false, + "uploadedBytes": 0, + "totalBytes": 0 + } +}); diff --git a/cms/static/js/views/textbooks.js b/cms/static/js/views/textbooks.js new file mode 100644 index 0000000000..f9fad1a6a5 --- /dev/null +++ b/cms/static/js/views/textbooks.js @@ -0,0 +1,333 @@ +CMS.Views.TextbookShow = Backbone.View.extend({ + initialize: function() { + this.template = _.template($("#show-textbook-tpl").text()); + this.listenTo(this.model, "change", this.render); + }, + tagName: "li", + events: { + "click .edit": "editTextbook", + "click .delete": "confirmDelete", + "click .show-chapters": "showChapters", + "click .hide-chapters": "hideChapters" + }, + render: function() { + this.$el.html(this.template(this.model.attributes)); + return this; + }, + editTextbook: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.model.collection.trigger("editOne", this.model); + }, + confirmDelete: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + var textbook = this.model, collection = this.model.collection; + var msg = new CMS.Models.WarningMessage({ + title: _.str.sprintf(gettext("Delete ā€œ%sā€?"), + textbook.escape('name')), + message: gettext("Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed."), + actions: { + primary: { + text: gettext("Delete"), + click: function(view) { + view.hide(); + collection.remove(textbook); + var delmsg = new CMS.Models.SystemFeedback({ + intent: "saving", + title: gettext("Deleting…") + }); + var notif = new CMS.Views.Notification({ + model: delmsg, + closeIcon: false, + minShown: 1250 + }); + collection.save({ + complete: function() { + notif.hide(); + } + }); + } + }, + secondary: [{ + text: gettext("Cancel"), + click: function(view) { + view.hide(); + } + }] + } + }); + var prompt = new CMS.Views.Prompt({model: msg}); + }, + showChapters: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.model.set('showChapters', true); + }, + hideChapters: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.model.set('showChapters', false); + } +}); +CMS.Views.TextbookEdit = Backbone.View.extend({ + initialize: function() { + this.template = _.template($("#new-textbook-tpl").text()); + var chapters = this.model.get('chapters'); + this.listenTo(chapters, "add", this.addOne); + this.listenTo(chapters, "reset", this.addAll); + this.listenTo(chapters, "all", this.render); + this.listenTo(this.model.collection, "editOne", this.remove); + }, + tagName: "li", + render: function() { + this.$el.html(this.template({ + name: this.model.escape('name'), + errors: null + })); + this.addAll(); + return this; + }, + events: { + "change input[name=textbook-name]": "setName", + "submit": "setAndClose", + "click .action-cancel": "cancel", + "click .action-add-chapter": "createChapter" + }, + addOne: function(chapter) { + var view = new CMS.Views.ChapterEdit({model: chapter}); + this.$("ol.chapters").append(view.render().el) + return this; + }, + addAll: function() { + this.model.get('chapters').each(this.addOne, this); + }, + createChapter: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.setValues(); + this.model.get('chapters').add([{}]); + }, + setName: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.model.set("name", this.$("#textbook-name-input").val(), {silent: true}); + }, + setValues: function() { + this.setName(); + var that = this; + _.each(this.$("li"), function(li, i) { + var chapter = that.model.get('chapters').at(i); + chapter.set({ + "name": $(".chapter-name", li).val(), + "asset_path": $(".chapter-asset-path", li).val() + }) + }); + return this; + }, + setAndClose: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.setValues(); + msg = new CMS.Models.SystemFeedback({ + intent: "saving", + title: gettext("Saving…") + }); + notif = new CMS.Views.Notification({ + model: msg, + closeIcon: false, + minShown: 1250 + }); + var that = this; + this.model.collection.save({ + success: function() { + that.close(); + }, + complete: function() { + notif.hide(); + } + }); + }, + cancel: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + return this.close(); + }, + close: function() { + var textbooks = this.model.collection; + delete textbooks.editing; + this.remove(); + // if the textbook has no content, remove it from the collection + if(this.model.isEmpty()) { + textbooks.remove(this.model); + } + textbooks.trigger('render'); + return this; + } +}); +CMS.Views.ListTextbooks = Backbone.View.extend({ + initialize: function() { + this.emptyTemplate = _.template($("#no-textbooks-tpl").text()); + this.listenTo(this.collection, 'all', this.render); + }, + tagName: "ul", + className: "textbooks", + render: function() { + var textbooks = this.collection; + if(textbooks.length === 0) { + this.$el.html(this.emptyTemplate()); + } else { + var $el = this.$el; + $el.empty(); + textbooks.each(function(textbook) { + var view; + if (textbook === textbooks.editing) { + view = new CMS.Views.TextbookEdit({model: textbook}); + } else { + view = new CMS.Views.TextbookShow({model: textbook}); + } + $el.append(view.render().el); + }); + } + return this; + }, + events: { + "click .new-button": "addOne" + }, + addOne: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + // if the existing edited textbook is empty, don't do anything + if(this.collection.editing && this.collection.editing.isEmpty()) { return; } + var m = new this.collection.model(); + this.collection.add(m); + this.collection.trigger("editOne", m); + } +}); +CMS.Views.ChapterEdit = Backbone.View.extend({ + initialize: function() { + this.template = _.template($("#new-chapter-tpl").text()); + this.listenTo(this.model, "change", this.render); + }, + tagName: "li", + className: function() { + return "field-group chapter chapter" + this.model.get('order'); + }, + render: function() { + this.$el.html(this.template({ + name: this.model.escape('name'), + asset_path: this.model.escape('asset_path'), + order: this.model.get('order') + })); + return this; + }, + events: { + "click .action-close": "removeChapter", + "click .action-upload": "openUploadDialog", + "submit": "uploadAsset" + }, + removeChapter: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.model.collection.remove(this.model); + return this.remove(); + }, + openUploadDialog: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + var msg = new CMS.Models.FileUpload({ + title: _.str.sprintf(gettext("Upload a new asset to %s"), + section.escape('name')), + message: "This is sample text about asset upload requirements, like no bigger than 2MB, must be in PDF format or whatever." + }); + var view = new CMS.Views.UploadDialog({model: msg, chapter: this.model}); + $(".wrapper-view").after(view.show().el); + } +}); + +CMS.Views.UploadDialog = Backbone.View.extend({ + options: { + shown: true + }, + initialize: function() { + this.template = _.template($("#upload-dialog-tpl").text()); + this.listenTo(this.model, "change", this.render); + }, + render: function() { + // some browsers (like Chrome) allow you to assign to the .files attribute + // of an DOM element -- for those browsers, we can + // create a new DOM element and assign the old content to it. Other browsers + // (like Firefox) make this attribute read-only, and we have to save the + // old DOM element in order to save it's content. For compatibility purposes, + // we'll just save the old element every time. + var oldInput = this.$("input[type=file]").get(0), selectedFile; + if (oldInput && oldInput.files.length) { + selectedFile = oldInput.files[0]; + } + this.$el.html(this.template({ + shown: this.options.shown, + url: UPLOAD_ASSET_CALLBACK_URL, + title: this.model.escape('title'), + message: this.model.escape('message'), + selectedFile: selectedFile, + uploading: this.model.get('uploading'), + uploadedBytes: this.model.get('uploadedBytes'), + totalBytes: this.model.get('totalBytes') + })); + if (oldInput) { + this.$('input[type=file]').replaceWith(oldInput); + } + + return this; + }, + events: { + "change input[type=file]": "selectFile", + "click .action-cancel": "hideAndRemove", + "click .action-upload": "upload" + }, + selectFile: function(e) { + this.model.set('fileList', e.target.files); + }, + show: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.options.shown = true; + $body.addClass('dialog-is-shown'); + return this.render(); + }, + hide: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + this.options.shown = false; + $body.removeClass('dialog-is-shown'); + return this.render(); + }, + hideAndRemove: function(e) { + if(e && e.preventDefault) { e.preventDefault(); } + return this.hide().remove(); + }, + upload: function(e) { + this.model.set('uploading', true); + this.$("form").ajaxSubmit({ + success: _.bind(this.success, this), + error: _.bind(this.error, this), + uploadProgress: _.bind(this.progress, this), + data: { + notifyOnError: false + } + }); + }, + progress: function(event, position, total, percentComplete) { + this.model.set({ + "uploadedBytes": position, + "totalBytes": total + }); + }, + success: function(response, statusText, xhr, form) { + this.model.set('uploading', false); + var chapter = this.options.chapter; + if(chapter) { + var options = {}; + if(!chapter.get("name")) { + options.name = response.displayname; + } + options.asset_path = response.url; + chapter.set(options); + } + this.remove(); + }, + error: function() { + this.model.set({ + "uploading": false, + "uploadedBytes": 0, + "title": gettext("We're sorry, there was an error") + }); + } +}); diff --git a/cms/templates/textbooks.html b/cms/templates/textbooks.html index 02b5f42a00..781ea21554 100644 --- a/cms/templates/textbooks.html +++ b/cms/templates/textbooks.html @@ -25,444 +25,12 @@ <%block name="jsextra"> + +