diff --git a/app/assets/javascripts/templateFolderForm.js b/app/assets/javascripts/templateFolderForm.js new file mode 100644 index 000000000..335a66115 --- /dev/null +++ b/app/assets/javascripts/templateFolderForm.js @@ -0,0 +1,114 @@ +(function(Modules) { + "use strict"; + + Modules.TemplateFolderForm = function() { + + this.start = function(templateFolderForm) { + this.$form = $(templateFolderForm); + + // remove the hidden unknown button - if you've got JS enabled then the action you want to do is implied by + // which field is visible. + this.$form.find('button[value=unknown]').remove(); + + this.$stickyBottom = this.$form.find('#sticky_template_forms'); + + this.$stickyBottom.append(this.nothingSelectedButtons); + this.$stickyBottom.append(this.itemsSelectedButtons); + + // all the diff states that we want to show or hide + this.states = [ + {key: 'nothing-selected-buttons', $el: this.$form.find('#nothing_selected'), cancellable: false}, + {key: 'items-selected-buttons', $el: this.$form.find('#items_selected'), cancellable: false}, + {key: 'move-to-existing-folder', $el: this.$form.find('#move_to_folder_radios'), cancellable: true}, + {key: 'move-to-new-folder', $el: this.$form.find('#move_to_new_folder_form'), cancellable: true}, + {key: 'add-new-folder', $el: this.$form.find('#add_new_folder_form'), cancellable: true}, + {key: 'add-new-template', $el: this.$form.find('#add_new_template_form'), cancellable: true} + ]; + + // cancel buttons only relevant if JS enabled, so + this.states.filter(state => state.cancellable).forEach((x) => this.addCancelButton(x)); + + // first off show the new template / new folder buttons + this.currentState = this.$form.data('prev-state') || 'unknown'; + if (this.currentState === 'unknown') { + this.selectActionButtons(); + } + + this.$form.on('click', 'button.button-secondary', (event) => this.actionButtonClicked(event)); + this.$form.on('change', 'input[type=checkbox]', () => this.templateFolderCheckboxChanged()); + + this.render(); + }; + + this.addCancelButton = function(state) { + let $cancel = $('') + .html('Cancel') + .click((event) => { + event.preventDefault(); + // clear existing data + state.$el.find('input:radio').prop('checked', false); + state.$el.find('input:text').val(''); + + // go back to action buttons + this.selectActionButtons(); + }); + + state.$el.append($cancel); + }; + + this.selectActionButtons = function () { + // If we want to show one of the grey choose actions state, we can pretend we're in the choose actions state, + // and then pretend a checkbox was clicked to work out whether to show zero or non-zero options. + // This calls a render at the end + this.currentState = 'nothing-selected-buttons'; + this.templateFolderCheckboxChanged(); + }; + + this.actionButtonClicked = function(event) { + event.preventDefault(); + this.currentState = $(event.currentTarget).val(); + + this.render(); + }; + + this.templateFolderCheckboxChanged = function() { + let numSelected = this.countSelectedCheckboxes(); + + if (this.currentState === 'nothing-selected-buttons' && numSelected !== 0) { + // user has just selected first item + this.currentState = 'items-selected-buttons'; + } else if (this.currentState === 'items-selected-buttons' && numSelected === 0) { + // user has just deselected last item + this.currentState = 'nothing-selected-buttons'; + } + + this.render(); + }; + + this.countSelectedCheckboxes = function() { + return this.$form.find('input:checkbox:checked').length; + }; + + this.render = function() { + // detach everything, unless they are the currentState + this.states.forEach( + state => (state.key === this.currentState ? this.$stickyBottom.append(state.$el) : state.$el.detach()) + ); + }; + + this.nothingSelectedButtons = ` +
+ + +
+ `; + + this.itemsSelectedButtons = ` +
+ + +
+ `; + }; + +})(window.GOVUK.Modules); diff --git a/app/main/forms.py b/app/main/forms.py index 874e277a6..08498f1cf 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -1165,11 +1165,11 @@ class TemplateAndFoldersSelectionForm(Form): * unknown currently not implemented, but in the future will try and work out if there are any obvious commands that can be assumed based on which fields are empty vs populated. - * move_to_existing_folder + * move-to-existing-folder must have data for templates_and_folders checkboxes, and move_to radios - * move_to_new_folder + * move-to-new-folder must have data for move_to_new_folder_name, cannot have data for move_to_existing_folder_name - * add_new_folder + * add-new-folder must have data for move_to_existing_folder_name, cannot have data for move_to_new_folder_name """ @@ -1212,12 +1212,15 @@ class TemplateAndFoldersSelectionForm(Form): ('copy-existing', 'Copy of an existing template') if allow_adding_copy_of_template else None, ])) + def is_selected(self, template_folder_id): + return template_folder_id in (self.templates_and_folders.data or []) + def validate(self): self.op = request.form.get('operation') - self.is_move_op = self.op in {'move_to_existing_folder', 'move_to_new_folder'} - self.is_add_folder_op = self.op in {'add_new_folder', 'move_to_new_folder'} - self.is_add_template_op = self.op in {'add_template'} + self.is_move_op = self.op in {'move-to-existing-folder', 'move-to-new-folder'} + self.is_add_folder_op = self.op in {'add-new-folder', 'move-to-new-folder'} + self.is_add_template_op = self.op in {'add-new-template'} if not (self.is_add_folder_op or self.is_move_op or self.is_add_template_op): return False @@ -1225,23 +1228,23 @@ class TemplateAndFoldersSelectionForm(Form): return super().validate() def get_folder_name(self): - if self.op == 'add_new_folder': + if self.op == 'add-new-folder': return self.add_new_folder_name.data - elif self.op == 'move_to_new_folder': + elif self.op == 'move-to-new-folder': return self.move_to_new_folder_name.data return None templates_and_folders = MultiCheckboxField('Choose templates or folders', validators=[ - required_for_ops('move_to_new_folder', 'move_to_existing_folder') + required_for_ops('move-to-new-folder', 'move-to-existing-folder') ]) move_to = RadioFieldWithNoneOption('Choose a folder', validators=[ Optional(), - required_for_ops('move_to_new_folder', 'move_to_existing_folder') + required_for_ops('move-to-new-folder', 'move-to-existing-folder') ]) - add_new_folder_name = StringField('Folder name', validators=[required_for_ops('add_new_folder')]) - move_to_new_folder_name = StringField('Folder name', validators=[required_for_ops('move_to_new_folder')]) + add_new_folder_name = StringField('Folder name', validators=[required_for_ops('add-new-folder')]) + move_to_new_folder_name = StringField('Folder name', validators=[required_for_ops('move-to-new-folder')]) add_template_by_template_type = RadioField('Add new', validators=[ Optional(), - required_for_ops('add_template') + required_for_ops('add-new-template') ]) diff --git a/app/main/views/templates.py b/app/main/views/templates.py index 5e4617ba9..151f301de 100644 --- a/app/main/views/templates.py +++ b/app/main/views/templates.py @@ -122,7 +122,6 @@ def choose_template(service_id, template_type='all', template_folder_id=None): len(user_api_client.get_service_ids_for_user(current_user)) > 1 ), ) - if request.method == 'POST' and templates_and_folders_form.validate_on_submit(): if not can_manage_folders(): abort(403) @@ -142,7 +141,7 @@ def choose_template(service_id, template_type='all', template_folder_id=None): template_nav_items=get_template_nav_items(template_folder_id), template_type=template_type, search_form=SearchTemplatesForm(), - templates_and_folders_form=templates_and_folders_form + templates_and_folders_form=templates_and_folders_form, ) diff --git a/app/templates/components/form.html b/app/templates/components/form.html index f01ee87f4..17cabc7f0 100644 --- a/app/templates/components/form.html +++ b/app/templates/components/form.html @@ -4,7 +4,8 @@ autocomplete=False, class=None, id=None, - module=None + module=None, + data_kwargs={} ) %}
{{ caller() }} diff --git a/app/templates/views/templates/_move_to.html b/app/templates/views/templates/_move_to.html index b8e35407e..27e8f1124 100644 --- a/app/templates/views/templates/_move_to.html +++ b/app/templates/views/templates/_move_to.html @@ -1,17 +1,19 @@ {% from "components/radios.html" import radios %} {% from "components/page-footer.html" import page_footer %} +
+ {% if templates_and_folders_form.move_to.choices and template_list.templates_to_show %}
{{ radios(templates_and_folders_form.move_to) }} - {{ page_footer('Move', button_name='operation', button_value='move_to_existing_folder') }} + {{ page_footer('Move', button_name='operation', button_value='move-to-existing-folder') }}
Move to a new folder {{ textbox(templates_and_folders_form.move_to_new_folder_name) }} - {{ page_footer('Move to a new folder', button_name='operation', button_value='move_to_new_folder') }} + {{ page_footer('Move to a new folder', button_name='operation', button_value='move-to-new-folder') }}
{% endif %} @@ -19,13 +21,14 @@
Add a new folder {{ textbox(templates_and_folders_form.add_new_folder_name) }} - {{ page_footer('New folder', button_name='operation', button_value='add_new_folder') }} + {{ page_footer('New folder', button_name='operation', button_value='add-new-folder') }}
Add a new template {{ radios(templates_and_folders_form.add_template_by_template_type) }} - {{ page_footer('Continue', button_name='operation', button_value='add_template') }} + {{ page_footer('Continue', button_name='operation', button_value='add-new-template') }}
+ diff --git a/app/templates/views/templates/_template_list.html b/app/templates/views/templates/_template_list.html index f26e32b0d..e4c030e76 100644 --- a/app/templates/views/templates/_template_list.html +++ b/app/templates/views/templates/_template_list.html @@ -17,6 +17,7 @@ {{ unlabelled_checkbox( id='templates-or-folder-{}'.format(item.id), name='templates_and_folders', + data=templates_and_folders_form.is_selected(item.id), value=item.id, ) }} {% endif %} diff --git a/app/templates/views/templates/choose.html b/app/templates/views/templates/choose.html index 8cba7139c..29234898d 100644 --- a/app/templates/views/templates/choose.html +++ b/app/templates/views/templates/choose.html @@ -60,8 +60,9 @@ {% if current_user.has_permissions('manage_templates') %}
+ {% if not can_manage_folders %} Add new template - + {% endif %} {% if can_manage_folders and current_template_folder_id %} Manage {% endif %} @@ -78,11 +79,12 @@ {{ live_search(target_selector='#template-list .template-list-item', show=show_search_box, form=search_form) }} {% if can_manage_folders %} - {% call form_wrapper() %} + {% call form_wrapper( + module='template-folder-form', + data_kwargs={'prev-state': templates_and_folders_form.op or None} + ) %} {% include 'views/templates/_template_list.html' %} - {% with templates=templates, template_folders=template_folders, templates_and_folders_form=templates_and_folders_form %} - {% include 'views/templates/_move_to.html' %} - {% endwith %} + {% include 'views/templates/_move_to.html' %} {% endcall %} {% else %} diff --git a/gulpfile.babel.js b/gulpfile.babel.js index 435b5dcf6..445bc988f 100644 --- a/gulpfile.babel.js +++ b/gulpfile.babel.js @@ -75,6 +75,7 @@ gulp.task('javascripts', () => gulp paths.src + 'javascripts/fullscreenTable.js', paths.src + 'javascripts/emailPreviewPane.js', paths.src + 'javascripts/colourPreview.js', + paths.src + 'javascripts/templateFolderForm.js', paths.src + 'javascripts/main.js' ]) .pipe(plugins.prettyerror()) diff --git a/tests/app/main/views/test_template_folders.py b/tests/app/main/views/test_template_folders.py index 6f0ff26d2..977d77994 100644 --- a/tests/app/main/views/test_template_folders.py +++ b/tests/app/main/views/test_template_folders.py @@ -797,10 +797,10 @@ def test_should_show_radios_and_buttons_for_move_destination_if_correct_permissi ] assert set(x['value'] for x in page.find_all('button', {'name': 'operation'})) == { 'unknown', - 'move_to_existing_folder', - 'move_to_new_folder', - 'add_new_folder', - 'add_template', + 'move-to-existing-folder', + 'move-to-new-folder', + 'add-new-folder', + 'add-new-template', } @@ -821,7 +821,7 @@ def test_should_be_able_to_move_to_existing_folder( 'main.choose_template', service_id=SERVICE_ONE_ID, _data={ - 'operation': 'move_to_existing_folder', + 'operation': 'move-to-existing-folder', 'move_to': PARENT_FOLDER_ID, 'templates_and_folders': [ FOLDER_TWO_ID, @@ -868,7 +868,7 @@ def test_should_not_be_able_to_move_to_existing_folder_if_dont_have_permission( 'main.choose_template', service_id=SERVICE_ONE_ID, _data={ - 'operation': 'move_to_existing_folder', + 'operation': 'move-to-existing-folder', 'move_to': PARENT_FOLDER_ID, 'templates_and_folders': [ FOLDER_TWO_ID, @@ -900,7 +900,7 @@ def test_should_be_able_to_move_a_sub_item( service_id=SERVICE_ONE_ID, template_folder_id=PARENT_FOLDER_ID, _data={ - 'operation': 'move_to_existing_folder', + 'operation': 'move-to-existing-folder', 'move_to': '__NONE__', 'templates_and_folders': [GRANDCHILD_FOLDER_ID], }, @@ -917,35 +917,35 @@ def test_should_be_able_to_move_a_sub_item( @pytest.mark.parametrize('data', [ # move to existing, but add new folder name given { - 'operation': 'move_to_existing_folder', + 'operation': 'move-to-existing-folder', 'templates_and_folders': [], 'add_new_folder_name': 'foo', 'move_to': PARENT_FOLDER_ID }, # move to existing, but move to new folder name given { - 'operation': 'move_to_existing_folder', + 'operation': 'move-to-existing-folder', 'templates_and_folders': [TEMPLATE_ONE_ID], 'move_to_new_folder_name': 'foo', 'move_to': PARENT_FOLDER_ID }, # move to existing, but no templates to move { - 'operation': 'move_to_existing_folder', + 'operation': 'move-to-existing-folder', 'templates_and_folders': [], 'move_to_new_folder_name': '', 'move_to': PARENT_FOLDER_ID }, # move to new, but nothing selected to move { - 'operation': 'move_to_new_folder', + 'operation': 'move-to-new-folder', 'templates_and_folders': [], 'move_to_new_folder_name': 'foo', 'move_to': None }, # add a new template, but also select move destination { - 'operation': 'add_template', + 'operation': 'add-new-template', 'templates_and_folders': [], 'move_to_new_folder_name': '', 'move_to': PARENT_FOLDER_ID, @@ -953,7 +953,7 @@ def test_should_be_able_to_move_a_sub_item( }, # add a new template, but also move to root folder { - 'operation': 'add_template', + 'operation': 'add-new-template', 'templates_and_folders': [], 'move_to_new_folder_name': '', 'move_to': '__NONE__', @@ -1019,7 +1019,7 @@ def test_new_folder_is_created_if_only_new_folder_is_filled_out( data = { 'move_to_new_folder_name': '', 'add_new_folder_name': 'new folder', - 'operation': 'add_new_folder' + 'operation': 'add-new-folder' } service_one['permissions'] += ['edit_folders'] @@ -1066,7 +1066,7 @@ def test_should_be_able_to_move_to_new_folder( service_id=SERVICE_ONE_ID, template_folder_id=None, _data={ - 'operation': 'move_to_new_folder', + 'operation': 'move-to-new-folder', 'move_to_new_folder_name': 'new folder', 'templates_and_folders': [ FOLDER_TWO_ID, diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py index 6ade834fc..09dee0ec0 100644 --- a/tests/app/main/views/test_templates.py +++ b/tests/app/main/views/test_templates.py @@ -616,7 +616,7 @@ def test_dont_show_preview_letter_templates_for_bad_filetype( 'template_type': 'copy-existing' }), ('main.choose_template', { - 'operation': 'add_template', + 'operation': 'add-new-template', 'add_template_by_template_type': 'copy-existing' }), )) @@ -791,7 +791,7 @@ def test_cant_copy_template_from_non_member_service( ( 'main.choose_template', { - 'operation': 'add_template', + 'operation': 'add-new-template', 'add_template_by_template_type': 'email', }, "Sending emails has been disabled for your service." @@ -799,7 +799,7 @@ def test_cant_copy_template_from_non_member_service( ( 'main.choose_template', { - 'operation': 'add_template', + 'operation': 'add-new-template', 'add_template_by_template_type': 'sms', }, "Sending text messages has been disabled for your service."