course_info_update.js 15.5 KB
Newer Older
1
define(['codemirror',
Eric Fischer committed
2 3 4 5 6 7 8 9
    'js/utils/modal',
    'js/utils/date_utils',
    'edx-ui-toolkit/js/utils/html-utils',
    'js/views/course_info_helper',
    'js/views/validation',
    'js/models/course_update',
    'common/js/components/views/feedback_prompt',
    'common/js/components/views/feedback_notification'],
10 11
    function(CodeMirror, ModalUtils, DateUtils, HtmlUtils, CourseInfoHelper, ValidatingView, CourseUpdateModel,
             PromptView, NotificationView) {
12 13
        'use strict';
        var CourseInfoUpdateView = ValidatingView.extend({
14

15
        // collection is CourseUpdateCollection
16 17 18 19 20 21 22 23 24 25
            events: {
                'click .new-update-button': 'onNew',
                'click .save-button': 'onSave',
                'click .cancel-button': 'onCancel',
                'click .post-actions > .edit-button': 'onEdit',
                'click .post-actions > .delete-button': 'onDelete'
            },

            initialize: function() {
                this.template = this.loadTemplate('course_info_update');
26 27

                // when the client refetches the updates as a whole, re-render them
28 29 30
                this.listenTo(this.collection, 'reset', this.render);
                this.listenTo(this.collection, 'invalid', this.handleValidationError);
            },
31

32
            render: function() {
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
                // iterate over updates and create views for each using the template
                var updateList = this.$el.find('#course-update-list'),
                    self = this;
                $(updateList).empty();
                if (this.collection.length > 0) {
                    this.collection.each(function(update, index) {
                        try {
                            CourseInfoHelper.changeContentToPreview(
                                update, 'content', self.options.base_asset_url);
                            // push notification is always disabled for existing updates
                            HtmlUtils.append(
                                updateList,
                                HtmlUtils.HTML(self.template({updateModel: update, push_notification_enabled: false}))
                            );
                            DateUtils.setupDatePicker('date', self, index);
                            update.isValid();
                        } catch (e) {
                            // ignore
                        } finally {
                            if (index === self.collection.length - 1) {
                                // Once the collection is loaded enable the button.
                                self.$el.find('.new-update-button').removeAttr('disabled');
                            }
                        }
                    });
                } else {
                    // If the collection is empty enable the New update button
                    self.$el.find('.new-update-button').removeAttr('disabled');
                }

                // Hide Update forms that are not for new updates with the editing class
                updateList.children().each(function(index, updateElement) {
                    var $updateElement = $(updateElement);
                    var updateForm = $updateElement.find('.new-update-form');
                    if ($updateElement.length > 0 && !$updateElement.hasClass('editing')) {
                        $(updateForm).hide();
69 70 71 72
                    }
                });
                return this;
            },
73

74 75 76
            collectionSelector: function(uid) {
                return 'course-update-list li[name=' + uid + ']';
            },
77

78 79
            setAndValidate: function(attr, value, event) {
                if (attr === 'date') {
80
                // If the value to be set was typed, validate that entry rather than the current datepicker value
81 82 83 84 85
                    if (this.dateEntry(event).length > 0) {
                        value = DateUtils.parseDateFromString(this.dateEntry(event).val());
                        if (value && isNaN(value.getTime())) {
                            value = '';
                        }
86
                    }
87
                    value = $.datepicker.formatDate('MM d, yy', value);
88
                }
89 90 91 92 93
                var targetModel = this.collection.get(this.$currentPost.attr('name'));
                var prevValue = targetModel.get(attr);
                if (prevValue !== value) {
                    targetModel.set(attr, value);
                    this.validateModel(targetModel);
94
                }
95
            },
96

97
            handleValidationError: function(model, error) {
98 99 100 101 102
                var self = this,
                    $validationElement = this.$el.find('#course-update-list li[name="' + model.cid + '"]');

                $validationElement.find('.message-error').remove();
                Object.keys(error).forEach(function(field) {
103
                    if (error.hasOwnProperty(field)) {
104 105 106 107 108 109 110 111
                        HtmlUtils.append(
                            $validationElement.find('#update-date-' + model.cid).parent(),
                            self.errorTemplate({message: error[field]})
                        );
                        HtmlUtils.append(
                            $validationElement.find('.date-display').parent(),
                            self.errorTemplate({message: error[field]})
                        );
112
                    }
113 114 115
                });

                $validationElement.find('.save-button').addClass('is-disabled');
116 117 118
            },

            validateModel: function(model) {
119
                var $validationElement = this.$el.find('#course-update-list li[name="' + model.cid + '"]');
120
                if (model.isValid()) {
121 122
                    $validationElement.find('.message-error').remove();
                    $validationElement.find('.save-button').removeClass('is-disabled');
123 124
                }
            },
125

126
            onNew: function(event) {
127
                // create new obj, insert into collection, and render this one ele overriding the hidden attr
128
                var newModel = new CourseUpdateModel();
129 130
                event.preventDefault();

131
                this.collection.add(newModel, {at: 0});
132

133
                var $newForm = $(
134
                this.template({
135 136
                    updateModel: newModel,
                    push_notification_enabled: this.options.push_notification_enabled
137
                })
138
                );
139

140 141
                var updateEle = this.$el.find('#course-update-list');
                $(updateEle).prepend($newForm);
142

143 144 145 146 147 148
                var $textArea = $newForm.find('.new-update-content').first();
                this.$codeMirror = CodeMirror.fromTextArea($textArea.get(0), {
                    mode: 'text/html',
                    lineNumbers: true,
                    lineWrapping: true
                });
149

150 151
                $newForm.addClass('editing');
                this.$currentPost = $newForm.closest('li');
152

153
                // Variable stored for unit test.
154
                this.$modalCover = ModalUtils.showModalCover(false, function() {
155
                // Binding empty function to prevent default hideModal.
156
                });
157

158 159
                DateUtils.setupDatePicker('date', this, 0);
            },
160

161 162 163 164
            onSave: function(event) {
                event.preventDefault();
                var targetModel = this.eventModel(event);
                targetModel.set({
165
                // translate short-form date (for input) into long form date (for display)
166 167 168 169
                    date: $.datepicker.formatDate('MM d, yy', new Date(this.dateEntry(event).val())),
                    content: this.$codeMirror.getValue(),
                    push_notification_selected: this.push_notification_selected(event)
                });
170
            // push change to display, hide the editor, submit the change
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
                var saving = new NotificationView.Mini({
                    title: gettext('Saving')
                });
                saving.show();
                var ele = this.modelDom(event);
                targetModel.save({}, {
                    success: function() {
                        saving.hide();
                    },
                    error: function() {
                        ele.remove();
                    }
                });
                this.closeEditor(false);

                analytics.track('Saved Course Update', {
Eric Fischer committed
187 188 189
                    course: course_location_analytics,
                    date: this.dateEntry(event).val(),
                    push_notification_selected: this.push_notification_selected(event)
190 191 192 193 194
                });
            },

            onCancel: function(event) {
                event.preventDefault();
195
            // Since we're cancelling, the model should be using it's previous attributes
196 197 198
                var targetModel = this.eventModel(event);
                targetModel.set(targetModel.previousAttributes());
                this.validateModel(targetModel);
199
            // Hide the editor
200
                $(this.editor(event)).hide();
201
            // targetModel will be lacking an id if it was newly created
202 203 204 205 206 207 208 209 210 211 212 213
                this.closeEditor(!targetModel.id);
            },

            onEdit: function(event) {
                event.preventDefault();
                var self = this;
                this.$currentPost = $(event.target).closest('li');
                this.$currentPost.addClass('editing');

                $(this.editor(event)).show();
                var $textArea = this.$currentPost.find('.new-update-content').first();
                var targetModel = this.eventModel(event);
214
            // translate long-form date (for viewing) into short-form date (for input)
215 216
                if (targetModel.get('date') && targetModel.isValid()) {
                    $(this.dateEntry(event)).val($.datepicker.formatDate('mm/dd/yy', new Date(targetModel.get('date'))));
Eric Fischer committed
217
                } else {
218 219 220
                    $(this.dateEntry(event)).val('MM/DD/YY');
                }
                this.$codeMirror = CourseInfoHelper.editWithCodeMirror(
221
                targetModel, 'content', self.options.base_asset_url, $textArea.get(0));
222

223
            // Variable stored for unit test.
224
                this.$modalCover = ModalUtils.showModalCover(false,
225
                function() {
226
                    self.closeEditor(false);
227 228
                }
            );
229 230

            // Ensure validity is marked appropriately
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
                targetModel.isValid();
            },

            onDelete: function(event) {
                event.preventDefault();

                var self = this;
                var targetModel = this.eventModel(event);
                var confirm = new PromptView.Warning({
                    title: gettext('Are you sure you want to delete this update?'),
                    message: gettext('This action cannot be undone.'),
                    actions: {
                        primary: {
                            text: gettext('OK'),
                            click: function() {
                                analytics.track('Deleted Course Update', {
Eric Fischer committed
247 248
                                    course: course_location_analytics,
                                    date: self.dateEntry(event).val()
249 250 251 252 253 254 255
                                });
                                self.modelDom(event).remove();
                                var deleting = new NotificationView.Mini({
                                    title: gettext('Deleting')
                                });
                                deleting.show();
                                targetModel.destroy({
256
                                    success: function() {
257 258
                                        self.collection.fetch({
                                            success: function() {
259 260 261
                                                self.render();
                                                deleting.hide();
                                            },
262 263 264 265 266 267 268 269 270 271 272 273
                                            reset: true
                                        });
                                    }
                                });
                                confirm.hide();
                            }
                        },
                        secondary: {
                            text: gettext('Cancel'),
                            click: function() {
                                confirm.hide();
                            }
274 275
                        }
                    }
276 277 278
                });
                confirm.show();
            },
279

280
            closeEditor: function(removePost) {
281 282
                var content,
                    targetModel = this.collection.get(this.$currentPost.attr('name'));
283

284 285
            // If the model was never created (user created a new update, then pressed Cancel),
            // we wish to remove it from the DOM.
286 287
                if (removePost) {
                    this.$currentPost.remove();
288
                } else {
289
                // close the modal and insert the appropriate data
290
                    this.$currentPost.removeClass('editing');
291
                    this.$currentPost.find('.date-display').text(targetModel.get('date'));
292
                    this.$currentPost.find('.date').val(targetModel.get('date'));
293

294 295 296
                    content = HtmlUtils.HTML(CourseInfoHelper.changeContentToPreview(
                        targetModel, 'content', this.options.base_asset_url
                    ));
297
                    try {
298
                    // just in case the content causes an error (embedded js errors)
299
                        HtmlUtils.setHtml(this.$currentPost.find('.update-contents'), content);
300 301
                        this.$currentPost.find('.new-update-content').val(content);
                    } catch (e) {
302
                    // ignore but handle rest of page
303 304 305
                    }
                    this.$currentPost.find('form').hide();
                    this.$currentPost.find('.CodeMirror').remove();
306 307

                // hide the push notification checkbox for subsequent edits to the Post
308 309 310 311
                    var push_notification_ele = this.$currentPost.find('.new-update-push-notification');
                    if (push_notification_ele) {
                        push_notification_ele.hide();
                    }
312
                }
313

314 315 316
                ModalUtils.hideModalCover(this.$modalCover);
                this.$codeMirror = null;
            },
317 318

        // Dereferencing from events to screen elements
319
            eventModel: function(event) {
320
            // not sure if it should be currentTarget or delegateTarget
321 322
                return this.collection.get($(event.currentTarget).attr('name'));
            },
323

324 325 326
            modelDom: function(event) {
                return $(event.currentTarget).closest('li');
            },
327

328 329 330 331 332 333
            editor: function(event) {
                var li = $(event.currentTarget).closest('li');
                if (li) {
                    return $(li).find('form').first();
                }
            },
334

335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
            dateEntry: function(event) {
                var li = $(event.currentTarget).closest('li');
                if (li) {
                    return $(li).find('.date').first();
                }
            },

            push_notification_selected: function(event) {
                var push_notification_checkbox;
                var li = $(event.currentTarget).closest('li');
                if (li) {
                    push_notification_checkbox = li.find('.new-update-push-notification .toggle-checkbox');
                    if (push_notification_checkbox) {
                        return push_notification_checkbox.is(':checked');
                    }
350 351
                }
            }
352
        });
353

354 355
        return CourseInfoUpdateView;
    }); // end define()