diff options
author | Eric Eastwood <contact@ericeastwood.com> | 2017-06-19 10:59:10 -0500 |
---|---|---|
committer | Eric Eastwood <contact@ericeastwood.com> | 2017-06-27 12:01:35 -0500 |
commit | aeb31904336a45eaa76641c0d5ecb17e68e24c42 (patch) | |
tree | 8c1a863226e4e2f7abe2e41b9415a9e3fb4ad972 | |
parent | 1dc1a7e9cea4eb42fef94a1cd571b91c5da404ee (diff) | |
download | gitlab-ce-32568-schedule-pipeline-with-variables-with-nested-form-gem.tar.gz |
WIP: Schedule pipelines with variables with nested_form gem32568-schedule-pipeline-with-variables-with-nested-form-gem
Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/32568
-rw-r--r-- | Gemfile | 1 | ||||
-rw-r--r-- | Gemfile.lock | 2 | ||||
-rw-r--r-- | app/assets/javascripts/commons/jquery.js | 1 | ||||
-rw-r--r-- | app/assets/stylesheets/pages/pipeline_schedules.scss | 74 | ||||
-rw-r--r-- | app/views/projects/pipeline_schedules/_form.html.haml | 51 | ||||
-rw-r--r-- | vendor/assets/javascripts/jquery_nested_form.js | 120 |
6 files changed, 216 insertions, 33 deletions
@@ -251,6 +251,7 @@ gem 'jquery-rails', '~> 4.1.0' gem 'request_store', '~> 1.3' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' +gem 'nested_form', '~> 0.3.2' gem 'net-ssh', '~> 3.0.1' gem 'base32', '~> 0.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index d77ba37f16f..2499b0d12cf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -474,6 +474,7 @@ GEM mustermann-grape (0.4.0) mustermann (= 0.4.0) mysql2 (0.3.20) + nested_form (0.3.2) net-ldap (0.12.1) net-ssh (3.0.1) netrc (0.11.0) @@ -1017,6 +1018,7 @@ DEPENDENCIES minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) mysql2 (~> 0.3.16) + nested_form (~> 0.3.2) net-ssh (~> 3.0.1) nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.4) diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js index b53f6284afc..9f3554bfa63 100644 --- a/app/assets/javascripts/commons/jquery.js +++ b/app/assets/javascripts/commons/jquery.js @@ -8,4 +8,5 @@ import 'vendor/jquery.atwho'; import 'vendor/jquery.scrollTo'; import 'vendor/jquery.nicescroll'; import 'vendor/jquery.waitforimages'; +import 'vendor/jquery_nested_form'; import 'select2/select2'; diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss index 595eb40fec7..234dad202bf 100644 --- a/app/assets/stylesheets/pages/pipeline_schedules.scss +++ b/app/assets/stylesheets/pages/pipeline_schedules.scss @@ -74,3 +74,77 @@ margin-right: 3px; } } + +.pipeline-variable-list { + margin-left: 0; + margin-bottom: 0; + padding-left: 0; +} + +.pipeline-variable-row { + display: flex; + margin-bottom: $gl-btn-padding; + + &:not(:last-child) { + margin-bottom: $gl-btn-padding; + } + + @media (max-width: $screen-xs-max) { + flex-wrap: wrap; + } + + & > .pipeline-variable-row-add-button { + display: none; + } + + &:last-of-type { + & > .pipeline-variable-row-remove-button { + display: none; + } + + & > .pipeline-variable-row-add-button { + display: inline-flex; + } + } +} + +.pipeline-variable-key-input { + margin-right: $gl-btn-padding; + + @media (max-width: $screen-xs-max) { + margin-right: calc(1em + #{2 * $gl-padding}); + margin-bottom: $gl-btn-padding; + } +} + +.pipeline-variable-value-input { + @media (max-width: $screen-xs-max) { + flex: 1; + } +} + +.pipeline-variable-row-remove-button { + display: inline-flex; + align-items: center; + padding: 0 $gl-padding; + background: transparent; + border: 0; + color: $gl-text-color-secondary; + + transition: color $general-hover-transition-duration $general-hover-transition-curve; + + &:hover, + &:focus { + outline: none; + text-decoration: none; + color: $gl-text-color; + } + + & > .fa { + width: 1em; + } +} + +.pipeline-variable-row-add-button { + @extend .pipeline-variable-row-remove-button; +} diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml index c079d5712e3..fc763df2e53 100644 --- a/app/views/projects/pipeline_schedules/_form.html.haml +++ b/app/views/projects/pipeline_schedules/_form.html.haml @@ -2,7 +2,7 @@ = webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'schedule_form' -= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f| += nested_form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f| = form_errors(@schedule) .form-group .col-md-9 @@ -22,38 +22,23 @@ = f.label :ref, _('Target Branch'), class: 'label-light' = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } ) = f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true - -# TODO: Test code - = @schedule.variables.inspect - - if @schedule.variables.present? - - @schedule.variables.each_with_index do |variable, i| - .form-group - .col-md-9 - %label.label-light Key - %input.form-control{:name => "schedule[variables_attributes][#{i}][key]", :type => "text", :value => variable.key}/ - %p.gl-field-error.hide This field is required. - %label.label-light Value - %input.form-control{:name => "schedule[variables_attributes][#{i}][value]", :type => "text", :value => variable.value}/ - %p.gl-field-error.hide This field is required. - - if @schedule.variables.count == 1 - - (1..1).each do |i| - .form-group - .col-md-9 - %label.label-light Key - %input.form-control{:name => "schedule[variables_attributes][#{i}][key]", :type => "text"}/ - %p.gl-field-error.hide This field is required. - %label.label-light Value - %input.form-control{:name => "schedule[variables_attributes][#{i}][value]", :type => "text"}/ - %p.gl-field-error.hide This field is required. - - else - - (0..0).each do |i| - .form-group - .col-md-9 - %label.label-light Key - %input.form-control{:name => "schedule[variables_attributes][#{i}][key]", :type => "text"}/ - %p.gl-field-error.hide This field is required. - %label.label-light Value - %input.form-control{:name => "schedule[variables_attributes][#{i}][value]", :type => "text"}/ - %p.gl-field-error.hide This field is required. + .form-group + .col-md-9 + %label.label-light + #{ _('Variables') } + %ul.js-pipeline-variable-list.pipeline-variable-list + = f.fields_for :variables, :wrapper => false do |variable_form| + %li.pipeline-variable-row.fields + = variable_form.text_field :key, class: 'pipeline-variable-key-input form-control', placeholder: _('Input variable key') + = variable_form.text_area :value, class: 'pipeline-variable-value-input form-control', rows: 1, placeholder: _('Input variable value') + = variable_form.link_to_remove class: 'pipeline-variable-row-remove-button' do + %i.fa.fa-minus-circle{ aria: { hidden: "true" } } + = f.link_to_add :variables, 'data-target': '.js-pipeline-variable-list', class: 'pipeline-variable-row-add-button' do + %i.fa.fa-plus-circle{ aria: { hidden: "true" } } + = f.link_to_add :variables, class: '', 'data-target': '.js-pipeline-variable-list' do + Add variable + %i.fa.fa-plus-circle{ aria: { hidden: "true" } } + .form-group .col-md-9 = f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light' diff --git a/vendor/assets/javascripts/jquery_nested_form.js b/vendor/assets/javascripts/jquery_nested_form.js new file mode 100644 index 00000000000..d62a03758f5 --- /dev/null +++ b/vendor/assets/javascripts/jquery_nested_form.js @@ -0,0 +1,120 @@ +(function($) { + window.NestedFormEvents = function() { + this.addFields = $.proxy(this.addFields, this); + this.removeFields = $.proxy(this.removeFields, this); + }; + + NestedFormEvents.prototype = { + addFields: function(e) { + // Setup + var link = e.currentTarget; + var assoc = $(link).data('association'); // Name of child + var blueprint = $('#' + $(link).data('blueprint-id')); + var content = blueprint.data('blueprint'); // Fields template + + // Make the context correct by replacing <parents> with the generated ID + // of each of the parent objects + var context = ($(link).closest('.fields').closestChild('input, textarea, select').eq(0).attr('name') || '').replace(/\[[a-z_]+\]$/, ''); + + // If the parent has no inputs we need to strip off the last pair + var current = content.match(new RegExp('\\[([a-z_]+)\\]\\[new_' + assoc + '\\]')); + if (current) { + context = context.replace(new RegExp('\\[' + current[1] + '\\]\\[(new_)?\\d+\\]$'), ''); + } + + // context will be something like this for a brand new form: + // project[tasks_attributes][1255929127459][assignments_attributes][1255929128105] + // or for an edit form: + // project[tasks_attributes][0][assignments_attributes][1] + if (context) { + var parentNames = context.match(/[a-z_]+_attributes(?=\]\[(new_)?\d+\])/g) || []; + var parentIds = context.match(/[0-9]+/g) || []; + + for(var i = 0; i < parentNames.length; i++) { + if(parentIds[i]) { + content = content.replace( + new RegExp('(_' + parentNames[i] + ')_.+?_', 'g'), + '$1_' + parentIds[i] + '_'); + + content = content.replace( + new RegExp('(\\[' + parentNames[i] + '\\])\\[.+?\\]', 'g'), + '$1[' + parentIds[i] + ']'); + } + } + } + + // Make a unique ID for the new child + var regexp = new RegExp('new_' + assoc, 'g'); + var new_id = this.newId(); + content = $.trim(content.replace(regexp, new_id)); + + var field = this.insertFields(content, assoc, link); + // bubble up event upto document (through form) + field + .trigger({ type: 'nested:fieldAdded', field: field }) + .trigger({ type: 'nested:fieldAdded:' + assoc, field: field }); + return false; + }, + newId: function() { + return new Date().getTime(); + }, + insertFields: function(content, assoc, link) { + var target = $(link).data('target'); + if (target) { + return $(content).appendTo($(target)); + } else { + return $(content).insertBefore(link); + } + }, + removeFields: function(e) { + var $link = $(e.currentTarget), + assoc = $link.data('association'); // Name of child to be removed + + var hiddenField = $link.prev('input[type=hidden]'); + hiddenField.val('1'); + + var field = $link.closest('.fields'); + field.hide(); + + field + .trigger({ type: 'nested:fieldRemoved', field: field }) + .trigger({ type: 'nested:fieldRemoved:' + assoc, field: field }); + return false; + } + }; + + window.nestedFormEvents = new NestedFormEvents(); + $(document) + .delegate('form a.add_nested_fields', 'click', nestedFormEvents.addFields) + .delegate('form a.remove_nested_fields', 'click', nestedFormEvents.removeFields); +})(jQuery); + +// http://plugins.jquery.com/project/closestChild +/* + * Copyright 2011, Tobias Lindig + * + * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) + * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. + * + */ +(function($) { + $.fn.closestChild = function(selector) { + // breadth first search for the first matched node + if (selector && selector != '') { + var queue = []; + queue.push(this); + while(queue.length > 0) { + var node = queue.shift(); + var children = node.children(); + for(var i = 0; i < children.length; ++i) { + var child = $(children[i]); + if (child.is(selector)) { + return child; //well, we found one + } + queue.push(child); + } + } + } + return $();//nothing found + }; +})(jQuery); |