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
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click ->
$("#file-content").val editor.getValue()
$(".file-editor form").submit()
......
......@@ -11,7 +11,6 @@ class @NewBlob
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
disableButtonIfEmptyField "#commit_message", ".js-commit-button"
$(".js-commit-button").click ->
$("#file-content").val editor.getValue()
$(".file-editor form").submit()
......
class @Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
@cleanBinding()
@addBinding()
@updateColorPreview()
......@@ -14,10 +13,6 @@ class @Labels
$(document).off 'click', '.suggest-colors a'
$(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
updateColorPreview: =>
previewColor = $('input#label_color').val()
......
......@@ -3,9 +3,3 @@ class @ProjectNew
$('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide()
$('.save-project-loader').show()
@initEvents()
initEvents: ->
disableButtonIfEmptyField '#project_name', '.project-submit'
......@@ -12,7 +12,7 @@
.form-group
= f.label :extern_uid, "Identifier", class: 'control-label'
.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
= f.submit 'Save changes', class: "btn btn-save"
......
......@@ -9,13 +9,10 @@
%strong= @ref
.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,
placeholder: 'Removed this file because...'
.form-group
.col-sm-offset-2.col-sm-10
= button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
disableButtonIfEmptyField('#commit_message', '.btn-remove-file')
......@@ -11,10 +11,9 @@
%i.fa.fa-eye
= 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 'shared/commit_message_container', params: params,
placeholder: "Update #{@blob.name}"
= render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}"
.form-group.branch
= label_tag 'branch', class: 'control-label' do
......@@ -25,8 +24,7 @@
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
= hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id]
= render 'projects/commit_button', ref: @ref,
cancel_path: @after_edit_path
= render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
- page_title "New File", @ref
%h3.page-title New file
.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 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
......
......@@ -6,7 +6,7 @@
%h3.page-title
%i.fa.fa-code-fork
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
= label_tag :branch_name, 'Name for new branch', class: 'control-label'
.col-sm-10
......@@ -20,7 +20,6 @@
= link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel'
:javascript
disableButtonIfAnyEmptyField($("#new-branch-form"), ".form-control", ".btn-create");
var availableTags = #{@project.repository.ref_names.to_json};
$("#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
- 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'}
.form-group
.input-group.inline-input-group
%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
.input-group.inline-input-group
%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;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if create_mr_button?
......@@ -26,5 +26,3 @@
source: availableTags,
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?
.row
.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
= render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
disableButtonIfEmptyField("#merge_request_title", ".btn-save");
$('.assign-to-me-link').on('click', function(e){
$('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
......
%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
.merge-request-branches.row
.col-md-6
......@@ -8,9 +8,9 @@
.panel-heading
%strong Source branch
.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;
= 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
.mr_source_commit
......@@ -20,9 +20,9 @@
%strong Target branch
.panel-body
- 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;
= 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
.mr_target_commit
......
......@@ -9,7 +9,7 @@
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%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
= render 'shared/issuable/form', f: f, issuable: @merge_request
= 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
.accept-merge-holder.clearfix.js-toggle-container
.accept-action
......@@ -25,8 +25,6 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
:coffeescript
disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
$('.accept-mr-form').on 'ajax:before', ->
btn = $('.accept_merge_request')
btn.disable()
......
......@@ -5,7 +5,7 @@
%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?
.alert.alert-danger
%ul
......@@ -16,7 +16,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.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
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
......@@ -45,7 +45,6 @@
:javascript
disableButtonIfEmptyField("#milestone_title", ".btn-save");
$( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
......
......@@ -4,8 +4,8 @@
.controls
= 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'
= button_tag class: 'btn btn-success btn-search-sha' do
%i.fa.fa-search
= button_tag class: 'btn btn-success' do
= icon('search')
.inline.prepend-left-20
.checkbox.light
= label_tag :filter_ref do
......@@ -16,8 +16,6 @@
= spinner nil, true
:javascript
disableButtonIfEmptyField('#extended_sha1', '.btn-search-sha')
network_graph = new Network({
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")}',
......
......@@ -5,13 +5,13 @@
= render 'projects/errors'
.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
= f.label :path, class: 'control-label' do
%strong Project path
.col-sm-10
.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
\.git
......
......@@ -26,7 +26,6 @@ Feature: Project Forked Merge Requests
#And I save the merge request
#Then I should see the edited merge request
@javascript
Scenario: I cannot submit an invalid merge request
Given I visit project "Forked Shop" merge requests page
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
window.disableButtonIfEmptyField = -> null
describe 'MergeRequest', ->
describe 'task lists', ->
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