Commit 83d8d611 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'rs-disables-submit-behavior' into 'master'

Add "Requires Input" JS behavior

This aims to replace all of those `disableButtonIfEmptyField` calls littered throughout views and JS with a simple, clean, markup-based behavior.

While going through all of the old usages, I found a few places where the old behavior wasn't actually working, so... bonus, they work now!

See merge request !900
parents 4642dcd5 ae75b2ee
# Requires Input behavior
#
# When called on a form with input fields with the `required` attribute, the
# form's submit button will be disabled until all required fields have values.
#
#= require extensions/jquery
#
# ### Example Markup
#
# <form class="js-requires-input">
# <input type="text" required="required">
# <input type="submit" value="Submit">
# </form>
#
$.fn.requiresInput = ->
$form = $(this)
$button = $('button[type=submit], input[type=submit]', $form)
required = '[required=required]'
fieldSelector = "input#{required}, select#{required}, textarea#{required}"
requireInput = ->
# Collect the input values of *all* required fields
values = _.map $(fieldSelector, $form), (field) -> field.value
# Disable the button if any required fields are empty
if values.length && _.any(values, _.isEmpty)
$button.disable()
else
$button.enable()
# Set initial button state
requireInput()
$form.on 'change input', fieldSelector, requireInput
# Triggered on standard document `ready` and on Turbolinks `page:load` events
$(document).on 'ready page:load', ->
$('form.js-requires-input').requiresInput()
...@@ -11,7 +11,6 @@ class @EditBlob ...@@ -11,7 +11,6 @@ class @EditBlob
if ace_mode if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click -> $(".js-commit-button").click ->
$("#file-content").val editor.getValue() $("#file-content").val editor.getValue()
$(".file-editor form").submit() $(".file-editor form").submit()
......
...@@ -11,7 +11,6 @@ class @NewBlob ...@@ -11,7 +11,6 @@ class @NewBlob
if ace_mode if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click -> $(".js-commit-button").click ->
$("#file-content").val editor.getValue() $("#file-content").val editor.getValue()
$(".file-editor form").submit() $(".file-editor form").submit()
......
class @Labels class @Labels
constructor: -> constructor: ->
form = $('.label-form') form = $('.label-form')
@setupLabelForm(form)
@cleanBinding() @cleanBinding()
@addBinding() @addBinding()
@updateColorPreview() @updateColorPreview()
...@@ -14,10 +13,6 @@ class @Labels ...@@ -14,10 +13,6 @@ class @Labels
$(document).off 'click', '.suggest-colors a' $(document).off 'click', '.suggest-colors a'
$(document).off 'input', 'input#label_color' $(document).off 'input', 'input#label_color'
# Initializes the form to disable the save button if no color or title is entered
setupLabelForm: (form) ->
disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button')
# Updates the the preview color with the hex-color input # Updates the the preview color with the hex-color input
updateColorPreview: => updateColorPreview: =>
previewColor = $('input#label_color').val() previewColor = $('input#label_color').val()
......
...@@ -3,9 +3,3 @@ class @ProjectNew ...@@ -3,9 +3,3 @@ class @ProjectNew
$('.project-edit-container').on 'ajax:before', => $('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide() $('.project-edit-container').hide()
$('.save-project-loader').show() $('.save-project-loader').show()
@initEvents()
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
.form-group .form-group
= f.label :extern_uid, "Identifier", class: 'control-label' = f.label :extern_uid, "Identifier", class: 'control-label'
.col-sm-10 .col-sm-10
= f.text_field :extern_uid, required: true, class: 'form-control', required: true = f.text_field :extern_uid, class: 'form-control', required: true
.form-actions .form-actions
= f.submit 'Save changes', class: "btn btn-save" = f.submit 'Save changes', class: "btn btn-save"
......
...@@ -9,13 +9,10 @@ ...@@ -9,13 +9,10 @@
%strong= @ref %strong= @ref
.modal-body .modal-body
= form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal' do = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do
= render 'shared/commit_message_container', params: params, = render 'shared/commit_message_container', params: params,
placeholder: 'Removed this file because...' placeholder: 'Removed this file because...'
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= button_tag 'Remove file', class: 'btn btn-remove btn-remove-file' = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
disableButtonIfEmptyField('#commit_message', '.btn-remove-file')
...@@ -11,10 +11,9 @@ ...@@ -11,10 +11,9 @@
%i.fa.fa-eye %i.fa.fa-eye
= editing_preview_title(@blob.name) = editing_preview_title(@blob.name)
= form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: "form-horizontal") do = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
= render 'shared/commit_message_container', params: params, = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}"
placeholder: "Update #{@blob.name}"
.form-group.branch .form-group.branch
= label_tag 'branch', class: 'control-label' do = label_tag 'branch', class: 'control-label' do
...@@ -25,8 +24,7 @@ ...@@ -25,8 +24,7 @@
= hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref, = render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path
cancel_path: @after_edit_path
:javascript :javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
- page_title "New File", @ref - page_title "New File", @ref
%h3.page-title New file %h3.page-title New file
.file-editor .file-editor
= form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file') do = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
= render 'projects/blob/editor', ref: @ref = render 'projects/blob/editor', ref: @ref
= render 'shared/commit_message_container', params: params, = render 'shared/commit_message_container', params: params,
placeholder: 'Add new file' placeholder: 'Add new file'
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
%h3.page-title %h3.page-title
%i.fa.fa-code-fork %i.fa.fa-code-fork
New branch New branch
= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal" do = form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do
.form-group .form-group
= label_tag :branch_name, 'Name for new branch', class: 'control-label' = label_tag :branch_name, 'Name for new branch', class: 'control-label'
.col-sm-10 .col-sm-10
...@@ -20,7 +20,6 @@ ...@@ -20,7 +20,6 @@
= link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel' = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript :javascript
disableButtonIfAnyEmptyField($("#new-branch-form"), ".form-control", ".btn-create");
var availableTags = #{@project.repository.ref_names.to_json}; var availableTags = #{@project.repository.ref_names.to_json};
$("#ref").autocomplete({ $("#ref").autocomplete({
......
= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline' do = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do
.clearfix.append-bottom-20 .clearfix.append-bottom-20
- if params[:to] && params[:from] - if params[:to] && params[:from]
= link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'}
.form-group .form-group
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon from %span.input-group-addon from
= text_field_tag :from, params[:from], class: "form-control" = text_field_tag :from, params[:from], class: "form-control", required: true
= "..." = "..."
.form-group .form-group
.input-group.inline-input-group .input-group.inline-input-group
%span.input-group-addon to %span.input-group-addon to
= text_field_tag :to, params[:to], class: "form-control" = text_field_tag :to, params[:to], class: "form-control", required: true
&nbsp; &nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn" = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if create_mr_button? - if create_mr_button?
...@@ -26,5 +26,3 @@ ...@@ -26,5 +26,3 @@
source: availableTags, source: availableTags,
minLength: 1 minLength: 1
}); });
disableButtonIfEmptyField('#to', '.commits-compare-btn');
= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
-if @label.errors.any? -if @label.errors.any?
.row .row
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
.merge-request-form-info .merge-request-form-info
= render 'shared/issuable/form', f: f, issuable: @merge_request = render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript :javascript
disableButtonIfEmptyField("#merge_request_title", ".btn-save");
$('.assign-to-me-link').on('click', function(e){ $('.assign-to-me-link').on('click', function(e){
$('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault(); e.preventDefault();
......
%p.lead Compare branches for new Merge Request %p.lead Compare branches for new Merge Request
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline" } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors .hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row .merge-request-branches.row
.col-md-6 .col-md-6
...@@ -8,9 +8,9 @@ ...@@ -8,9 +8,9 @@
.panel-heading .panel-heading
%strong Source branch %strong Source branch
.panel-body .panel-body
= f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? }) = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp; &nbsp;
= f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'}) = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2', required: true})
.panel-footer .panel-footer
.mr_source_commit .mr_source_commit
...@@ -20,9 +20,9 @@ ...@@ -20,9 +20,9 @@
%strong Target branch %strong Target branch
.panel-body .panel-body
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project] - projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
= f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? }) = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp; &nbsp;
= f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'}) = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2', required: true})
.panel-footer .panel-footer
.mr_target_commit .mr_target_commit
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%span.pull-right %span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request) = link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr %hr
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f| = form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
.merge-request-form-info .merge-request-form-info
= render 'shared/issuable/form', f: f, issuable: @merge_request = render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id = f.hidden_field :source_project_id
......
= form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form' } do |f| = form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token = hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container .accept-merge-holder.clearfix.js-toggle-container
.accept-action .accept-action
...@@ -25,8 +25,6 @@ ...@@ -25,8 +25,6 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
:coffeescript :coffeescript
disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
$('.accept-mr-form').on 'ajax:before', -> $('.accept-mr-form').on 'ajax:before', ->
btn = $('.accept_merge_request') btn = $('.accept_merge_request')
btn.disable() btn.disable()
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
%hr %hr
= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f| = form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f|
-if @milestone.errors.any? -if @milestone.errors.any?
.alert.alert-danger .alert.alert-danger
%ul %ul
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.form-group .form-group
= f.label :title, "Title", class: "control-label" = f.label :title, "Title", class: "control-label"
.col-sm-10 .col-sm-10
= f.text_field :title, maxlength: 255, class: "form-control" = f.text_field :title, maxlength: 255, class: "form-control", required: true
%p.hint Required %p.hint Required
.form-group.milestone-description .form-group.milestone-description
= f.label :description, "Description", class: "control-label" = f.label :description, "Description", class: "control-label"
...@@ -45,7 +45,6 @@ ...@@ -45,7 +45,6 @@
:javascript :javascript
disableButtonIfEmptyField("#milestone_title", ".btn-save");
$( ".datepicker" ).datepicker({ $( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd", dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
.controls .controls
= form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f|
= text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha' = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha'
= button_tag class: 'btn btn-success btn-search-sha' do = button_tag class: 'btn btn-success' do
%i.fa.fa-search = icon('search')
.inline.prepend-left-20 .inline.prepend-left-20
.checkbox.light .checkbox.light
= label_tag :filter_ref do = label_tag :filter_ref do
...@@ -16,8 +16,6 @@ ...@@ -16,8 +16,6 @@
= spinner nil, true = spinner nil, true
:javascript :javascript
disableButtonIfEmptyField('#extended_sha1', '.btn-search-sha')
network_graph = new Network({ network_graph = new Network({
url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}', url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}',
commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}', commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}',
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
= render 'projects/errors' = render 'projects/errors'
.project-edit-content .project-edit-content
= form_for @project, html: { class: 'new_project form-horizontal' } do |f| = form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f|
.form-group.project-name-holder .form-group.project-name-holder
= f.label :path, class: 'control-label' do = f.label :path, class: 'control-label' do
%strong Project path %strong Project path
.col-sm-10 .col-sm-10
.input-group .input-group
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true
.input-group-addon .input-group-addon
\.git \.git
......
...@@ -26,7 +26,6 @@ Feature: Project Forked Merge Requests ...@@ -26,7 +26,6 @@ Feature: Project Forked Merge Requests
#And I save the merge request #And I save the merge request
#Then I should see the edited merge request #Then I should see the edited merge request
@javascript
Scenario: I cannot submit an invalid merge request Scenario: I cannot submit an invalid merge request
Given I visit project "Forked Shop" merge requests page Given I visit project "Forked Shop" merge requests page
And I click link "New Merge Request" And I click link "New Merge Request"
......
#= require behaviors/requires_input
describe 'requiresInput', ->
fixture.preload('behaviors/requires_input.html')
beforeEach ->
fixture.load('behaviors/requires_input.html')
it 'disables submit when any field is required', ->
$('.js-requires-input').requiresInput()
expect($('.submit')).toBeDisabled()
it 'enables submit when no field is required', ->
$('*[required=required]').removeAttr('required')
$('.js-requires-input').requiresInput()
expect($('.submit')).not.toBeDisabled()
it 'enables submit when all required fields are pre-filled', ->
$('*[required=required]').remove()
$('.js-requires-input').requiresInput()
expect($('.submit')).not.toBeDisabled()
it 'enables submit when all required fields receive input', ->
$('.js-requires-input').requiresInput()
$('#required1').val('input1').change()
expect($('.submit')).toBeDisabled()
$('#optional1').val('input1').change()
expect($('.submit')).toBeDisabled()
$('#required2').val('input2').change()
$('#required3').val('input3').change()
$('#required4').val('input4').change()
$('#required5').val('1').change()
expect($('.submit')).not.toBeDisabled()
it 'is called on page:load event', ->
spy = spyOn($.fn, 'requiresInput')
$(document).trigger('page:load')
expect(spy).toHaveBeenCalled()
%form.js-requires-input
%input{type: 'text', id: 'required1', required: 'required'}
%input{type: 'text', id: 'required2', required: 'required'}
%input{type: 'text', id: 'required3', required: 'required', value: 'Pre-filled'}
%input{type: 'text', id: 'optional1'}
%textarea{id: 'required4', required: 'required'}
%textarea{id: 'optional2'}
%select{id: 'required5', required: 'required'}
%option Zero
%option{value: '1'} One
%select{id: 'optional3', required: 'required'}
%option Zero
%option{value: '1'} One
%button.submit{type: 'submit', value: 'Submit'}
%input.submit{type: 'submit', value: 'Submit'}
#= require merge_request #= require merge_request
window.disableButtonIfEmptyField = -> null
describe 'MergeRequest', -> describe 'MergeRequest', ->
describe 'task lists', -> describe 'task lists', ->
fixture.preload('merge_requests_show.html') fixture.preload('merge_requests_show.html')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment