From 55bfb3103ee230851d30b0d51c2b89d7490df848 Mon Sep 17 00:00:00 2001 From: goldpbear Date: Thu, 22 Sep 2016 11:30:45 -0700 Subject: [PATCH 1/3] Refactor dynamic form so appropriate model is added on form submit Previously, the dynamic form created and destroyed models as users cycled among form categories. This generated a lot of intermediary junk models. Instead, let's hold off on model instantiation until the user submits the form. Note this also means we need to hold off on instantiating any attachment models until form submission as well. --- src/sa_web/static/js/views/place-form-view.js | 116 +++++++----------- 1 file changed, 47 insertions(+), 69 deletions(-) diff --git a/src/sa_web/static/js/views/place-form-view.js b/src/sa_web/static/js/views/place-form-view.js index f3f989598f..091261fe2a 100644 --- a/src/sa_web/static/js/views/place-form-view.js +++ b/src/sa_web/static/js/views/place-form-view.js @@ -19,21 +19,15 @@ var Shareabouts = Shareabouts || {}; this.formState = { selectedCategory: null, selectedDatasetId: null, - priorDatasetId: null, selectedDatasetSlug: null, - priorModelCid: null, isSingleCategory: false, + attachmentData: {}, placeDetail: this.options.placeConfig.place_detail } S.TemplateHelpers.overridePlaceTypeConfig(this.options.placeConfig.items, this.options.defaultPlaceTypeName); S.TemplateHelpers.insertInputTypeFlags(this.options.placeConfig.items); - - // attach collection listeners - for (var collection in this.collection) { - this.collection[collection].on('add', self.setModel, this); - } }, render: function(category, isCategorySelected) { var self = this, @@ -49,7 +43,6 @@ var Shareabouts = Shareabouts || {}; this.formState.selectedDatasetId = placesToIncludeOnForm[0].dataset; this.formState.selectedDatasetSlug = _.find(this.options.mapConfig.layers, function(layer) { return self.formState.selectedDatasetId == layer.id }).slug; selectedCategoryConfig = placesToIncludeOnForm[0]; - this.collection[this.formState.selectedDatasetId].add({}); } var data = _.extend({ @@ -133,9 +126,6 @@ var Shareabouts = Shareabouts || {}; $("#category-btns").animate( { height: "hide" }, animationDelay ); // if we've already dragged the map, make sure the map drag instructions don't reappear if (this.center) this.$('.drag-marker-instructions, .drag-marker-warning').addClass('is-visuallyhidden'); - - // instantiate appropriate backbone model - this.collection[self.formState.selectedDatasetId].add({"location_type": this.formState.selectedCategory}); }, onClickGeolocate: function(evt) { var self = this; @@ -167,21 +157,10 @@ var Shareabouts = Shareabouts || {}; this.$('.fileinput-name').text(file.name); S.Util.fileToCanvas(file, function(canvas) { canvas.toBlob(function(blob) { - var fieldName = $(evt.target).attr('name'), - data = { - name: fieldName, - blob: blob, - file: canvas.toDataURL('image/jpeg') - }; - - attachment = self.model.attachmentCollection.find(function(model) { - return model.get('name') === fieldName; - }); - - if (_.isUndefined(attachment)) { - self.model.attachmentCollection.add(data); - } else { - attachment.set(data); + self.formState.attachmentData = { + name: $(evt.target).attr('name'), + blob: blob, + file: canvas.toDataURL('image/jpeg') } }, 'image/jpeg'); }, { @@ -206,21 +185,8 @@ var Shareabouts = Shareabouts || {}; $(evt.target).val(altContent.value); $(evt.target).next("label").html(altContent.label); }, - setModel: function(model) { - var self = this; - this.model = model; - - if (this.formState.priorModelCid && this.formState.priorDatasetId) { - this.collection[self.formState.priorDatasetId].get({ cid: self.formState.priorModelCid }).destroy(); - } - this.formState.priorModelCid = model.cid; - this.formState.priorDatasetId = this.formState.selectedDatasetId; - }, closePanel: function() { this.center = null; - // make sure we reset priorModelCid and priorDatasetId if the user closes the side panel - this.formState.priorModelCid = null; - this.formState.priorDatasetId = null; }, onExpandCategories: function(evt) { var animationDelay = 400; @@ -244,44 +210,56 @@ var Shareabouts = Shareabouts || {}; var self = this, router = this.options.router, - model = this.model, // Should not include any files attrs = this.getAttrs(), - categoryId = $(evt.target), $button = this.$('[name="save-place-btn"]'), spinner, $fileInputs; - - model.set("datasetSlug", this.formState.selectedDatasetSlug); - model.set("datasetId", this.formState.selectedDatasetId); evt.preventDefault(); - $button.attr('disabled', 'disabled'); - spinner = new Spinner(S.smallSpinnerOptions).spin(this.$('.form-spinner')[0]); - - S.Util.log('USER', 'new-place', 'submit-place-btn-click'); - - S.Util.setStickyFields(attrs, S.Config.survey.items, S.Config.place.items); - - // Save and redirect - this.model.save(attrs, { - success: function() { - S.Util.log('USER', 'new-place', 'successfully-add-place'); - - // add the newly-created model to mergedPlaces, - // for use on the place list view - self.options.appView.mergedPlaces.add(model); + this.collection[self.formState.selectedDatasetId].on("add", function(model) { + model.set("datasetSlug", self.formState.selectedDatasetSlug); + model.set("datasetId", self.formState.selectedDatasetId); + + var attachment = model.attachmentCollection.find(function(attachmentModel) { + return attachmentModel.get('name') === fieldName; + }); - router.navigate('/'+ model.get('datasetSlug') + '/' + model.id, {trigger: true}); - }, - error: function() { - S.Util.log('USER', 'new-place', 'fail-to-add-place'); - }, - complete: function() { - $button.removeAttr('disabled'); - spinner.stop(); - }, - wait: true + if (_.isUndefined(attachment)) { + model.attachmentCollection.add(self.formState.attachmentData); + } else { + attachment.set(self.formState.attachmentData); + } + + $button.attr('disabled', 'disabled'); + spinner = new Spinner(S.smallSpinnerOptions).spin(self.$('.form-spinner')[0]); + + S.Util.log('USER', 'new-place', 'submit-place-btn-click'); + + S.Util.setStickyFields(attrs, S.Config.survey.items, S.Config.place.items); + + // Save and redirect + model.save(attrs, { + success: function() { + S.Util.log('USER', 'new-place', 'successfully-add-place'); + + // add the newly-created model to mergedPlaces, + // for use on the place list view + self.options.appView.mergedPlaces.add(model); + + router.navigate('/'+ model.get('datasetSlug') + '/' + model.id, {trigger: true}); + }, + error: function() { + S.Util.log('USER', 'new-place', 'fail-to-add-place'); + }, + complete: function() { + $button.removeAttr('disabled'); + spinner.stop(); + }, + wait: true + }); }); + + this.collection[self.formState.selectedDatasetId].add({"location_type": this.formState.selectedCategory}); }) }); }(Shareabouts, jQuery, Shareabouts.Util.console)); From e7a0cd1001a7203c680b832571fc538caa31b823 Mon Sep 17 00:00:00 2001 From: goldpbear Date: Thu, 22 Sep 2016 13:22:30 -0700 Subject: [PATCH 2/3] Abstract out form state reset code Make sure to call a form state reset when the form is initialized, when a model is successfully submitted, and when the form panel is closed. --- src/sa_web/static/js/views/place-form-view.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/sa_web/static/js/views/place-form-view.js b/src/sa_web/static/js/views/place-form-view.js index 091261fe2a..ecfa0d4773 100644 --- a/src/sa_web/static/js/views/place-form-view.js +++ b/src/sa_web/static/js/views/place-form-view.js @@ -14,20 +14,22 @@ var Shareabouts = Shareabouts || {}; }, initialize: function(){ var self = this; - // keep track of relevant catgory & dataset info - // as user switches among categories + + this.resetFormState(); + + S.TemplateHelpers.overridePlaceTypeConfig(this.options.placeConfig.items, + this.options.defaultPlaceTypeName); + S.TemplateHelpers.insertInputTypeFlags(this.options.placeConfig.items); + }, + resetFormState: function() { this.formState = { selectedCategory: null, selectedDatasetId: null, selectedDatasetSlug: null, isSingleCategory: false, - attachmentData: {}, + attachmentData: null, placeDetail: this.options.placeConfig.place_detail - } - - S.TemplateHelpers.overridePlaceTypeConfig(this.options.placeConfig.items, - this.options.defaultPlaceTypeName); - S.TemplateHelpers.insertInputTypeFlags(this.options.placeConfig.items); + } }, render: function(category, isCategorySelected) { var self = this, @@ -187,6 +189,7 @@ var Shareabouts = Shareabouts || {}; }, closePanel: function() { this.center = null; + this.resetFormState(); }, onExpandCategories: function(evt) { var animationDelay = 400; From c4ec3205f0fe4f0789262d07287ef6e0d0915379 Mon Sep 17 00:00:00 2001 From: goldpbear Date: Thu, 22 Sep 2016 13:23:45 -0700 Subject: [PATCH 3/3] Don't use a collection listener in onSubmit Putting a collection listener inside Gatekeeper.onValidSubmit creates a closure, and the listener is retained during future uses of the place form view. Instead, get a reference to the last model generated in the collection by calling .at(). --- src/sa_web/static/js/views/place-form-view.js | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/sa_web/static/js/views/place-form-view.js b/src/sa_web/static/js/views/place-form-view.js index ecfa0d4773..1a3397df46 100644 --- a/src/sa_web/static/js/views/place-form-view.js +++ b/src/sa_web/static/js/views/place-form-view.js @@ -213,18 +213,24 @@ var Shareabouts = Shareabouts || {}; var self = this, router = this.options.router, + collection = this.collection[self.formState.selectedDatasetId], + model, // Should not include any files attrs = this.getAttrs(), $button = this.$('[name="save-place-btn"]'), spinner, $fileInputs; evt.preventDefault(); - this.collection[self.formState.selectedDatasetId].on("add", function(model) { - model.set("datasetSlug", self.formState.selectedDatasetSlug); - model.set("datasetId", self.formState.selectedDatasetId); - + collection.add({"location_type": this.formState.selectedCategory}); + model = collection.at(collection.length - 1); + + model.set("datasetSlug", self.formState.selectedDatasetSlug); + model.set("datasetId", self.formState.selectedDatasetId); + + // if an attachment has been added... + if (self.formState.attachmentData) { var attachment = model.attachmentCollection.find(function(attachmentModel) { - return attachmentModel.get('name') === fieldName; + return attachmentModel.get('name') === self.formState.attachmentData.name; }); if (_.isUndefined(attachment)) { @@ -232,37 +238,36 @@ var Shareabouts = Shareabouts || {}; } else { attachment.set(self.formState.attachmentData); } + } - $button.attr('disabled', 'disabled'); - spinner = new Spinner(S.smallSpinnerOptions).spin(self.$('.form-spinner')[0]); + $button.attr('disabled', 'disabled'); + spinner = new Spinner(S.smallSpinnerOptions).spin(self.$('.form-spinner')[0]); - S.Util.log('USER', 'new-place', 'submit-place-btn-click'); + S.Util.log('USER', 'new-place', 'submit-place-btn-click'); - S.Util.setStickyFields(attrs, S.Config.survey.items, S.Config.place.items); + S.Util.setStickyFields(attrs, S.Config.survey.items, S.Config.place.items); - // Save and redirect - model.save(attrs, { - success: function() { - S.Util.log('USER', 'new-place', 'successfully-add-place'); + // Save and redirect + model.save(attrs, { + success: function() { + S.Util.log('USER', 'new-place', 'successfully-add-place'); - // add the newly-created model to mergedPlaces, - // for use on the place list view - self.options.appView.mergedPlaces.add(model); + // add the newly-created model to mergedPlaces, + // for use on the place list view + self.options.appView.mergedPlaces.add(model); - router.navigate('/'+ model.get('datasetSlug') + '/' + model.id, {trigger: true}); - }, - error: function() { - S.Util.log('USER', 'new-place', 'fail-to-add-place'); - }, - complete: function() { - $button.removeAttr('disabled'); - spinner.stop(); - }, - wait: true - }); + router.navigate('/'+ model.get('datasetSlug') + '/' + model.id, {trigger: true}); + }, + error: function() { + S.Util.log('USER', 'new-place', 'fail-to-add-place'); + }, + complete: function() { + $button.removeAttr('disabled'); + spinner.stop(); + self.resetFormState(); + }, + wait: true }); - - this.collection[self.formState.selectedDatasetId].add({"location_type": this.formState.selectedCategory}); }) }); }(Shareabouts, jQuery, Shareabouts.Util.console));