Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ticket 308208 v1,[Modèles de demande] Nouveau type de champ "Liste de boutons avec icones" #38

Closed
wants to merge 10 commits into from
4 changes: 2 additions & 2 deletions .github/workflows/5_1_0.yml → .github/workflows/5_1_1.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: Tests 5.1.0
name: Tests 5.1.1

env:
PLUGIN_NAME: redmine_templates
REDMINE_VERSION: 5.1.0
REDMINE_VERSION: 5.1.1
RAILS_ENV: test

on:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ You will also need a recent version of Ruby:
|Plugin branch| Redmine Version | Test Status |
|-------------|-----------------|-------------------|
|master | 4.2.11 | [![4.2.11][1]][5] |
|master | 5.1.0 | [![5.1.0][2]][5] |
|master | 5.1.1 | [![5.1.1][2]][5] |
|master | master | [![master][4]][5] |

[1]: https://github.com/nanego/redmine_templates/actions/workflows/4_2_11.yml/badge.svg
[2]: https://github.com/nanego/redmine_templates/actions/workflows/5_1_0.yml/badge.svg
[2]: https://github.com/nanego/redmine_templates/actions/workflows/5_1_1.yml/badge.svg
[4]: https://github.com/nanego/redmine_templates/actions/workflows/master.yml/badge.svg
[5]: https://github.com/nanego/redmine_templates/actions
47 changes: 44 additions & 3 deletions app/models/issue_template_section.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ class IssueTemplateSection < ActiveRecord::Base

belongs_to :issue_template_section_group

DEFAULT_ICON = "alert-fill"

DISPLAY_MODES = [:all_values, :selected_values_only]

acts_as_list scope: [:issue_template_section_group_id]
Expand Down Expand Up @@ -73,8 +75,8 @@ class IssueTemplateSectionCheckbox < IssueTemplateSection
def self.short_name
"checkbox"
end

def rendered_value(section_attributes, textile: true, value_only: false)

value = value_from_boolean_attribute(section_attributes[:text])
if value_only
section_basic_entry(value, textile: textile)
Expand Down Expand Up @@ -137,8 +139,47 @@ def rendered_value(section_attributes, textile: true, value_only: false)

class IssueTemplateSectionSelect < IssueTemplateSection
validates_presence_of :title
before_validation :validate_labels_and_set_default_icon
# Accessor for the buttons_icons attribute
# Used for custom error message in case of validation failure
attr_accessor :buttons_icons_field

# Validates the presence of labels
# Replaces empty elements in the icon_name string with the "default value" before validation.
#
# icon_name - The string containing icon names separated by semicolons.
#
# Examples
#
# icon_name = ";icon1;;icon3;"
#
# # => ";alert-fill;icon1;alert-fill;icon3;alert-fill;"
#
# Returns the modified icon_name string.
def validate_labels_and_set_default_icon
if select_type == "buttons_icons"
# Validate the presence of labels in the text string
values = text.split(";", -1)

values.each_with_index do |value, key|
# Check if the value is empty
if value.blank?
errors.add(:buttons_icons_field, ::I18n.t('label_value_at_index', :title => title, :key => key))
end
end

# Replace empty values in the icon_name string with "default-value"
icons = icon_name.split(";" , -1)

TYPES = [:checkbox, :radio, :monovalue_select, :multivalue_select]
icons.each_with_index do |value, key|
icons[key] = DEFAULT_ICON if value.blank?
end

self.icon_name = icons.join(";")

end
end
TYPES = [:checkbox, :radio, :monovalue_select, :multivalue_select, :buttons_icons]

after_initialize do
self.select_type ||= :checkbox
Expand All @@ -155,7 +196,7 @@ def self.short_name

def rendered_value(section_attributes, textile: true, value_only: false)
case self.select_type
when "monovalue_select", "radio"
when "monovalue_select", "radio", "buttons_icons"
value = value_from_text_attribute(section_attributes)
if value_only
section_basic_entry(value, textile: textile)
Expand Down
118 changes: 108 additions & 10 deletions app/views/issue_templates/_section_groups_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,24 @@
resetHiddenTextField(sectionElement)
resetHiddenPlaceholderField(sectionElement)
resetEmptyValueField(sectionElement)
resetHiddenIconField(sectionElement)
}

function resetHiddenTextField(sectionElement) {
let text = $(sectionElement).find('input.text')
let icon_name = $(sectionElement).find('input.icon_name');

// Object (passed by reference)
let icon_object = { values : icon_name.val() }

let ul = $(sectionElement).find('ul')
let joined_values = ul.find('li .value').map(function () {
return $(this).val()
}).get().filter(item => item).join(';')
text.val(joined_values)
}).get().join(';')

let text_object = { values : joined_values }
removeEmptyElementsValuesFromTwoObjects(text_object, icon_object)
text.val(text_object.values)
}

function resetHiddenPlaceholderField(sectionElement) {
Expand All @@ -34,13 +43,32 @@
placeholder.val(joined_values)
}

function resetHiddenIconField(sectionElement) {
let icon_name = $(sectionElement).find('input.icon_name')
let text = $(sectionElement).find('input.text')

// Object (passed by reference)
let text_object = { values : text.val() }

let ul = $(sectionElement).find('ul')
let joined_values = ul.find('li .buttons-icons').map(function () {
return $(this).val()
}).get().join(';')

// Object (passed by reference)
let icon_object = { values : joined_values }
removeEmptyElementsValuesFromTwoObjects(icon_object,text_object)
icon_name.val(icon_object.values)
}

function resetEmptyValueField(sectionElement) {
let empty_value = $(sectionElement).find('input.empty_value')
let list_of_value = $(sectionElement).find('ul')
let joined_values = list_of_value.find('li input[type=checkbox][name=read_only]:checked').map(function () {
return $(this).parent().find('.value').val()
}).get().filter(item => item).join(';')
empty_value.val(joined_values)
//let type = $(sectionElement).find('#type')[0]
let empty_value = $(sectionElement).find('input.empty_value')
let list_of_value = $(sectionElement).find('ul')
let joined_values = list_of_value.find('li input[type=checkbox][name=read_only]:checked').map(function () {
return $(this).parent().find('.value').val()
}).get().filter(item => item).join(';')
empty_value.val(joined_values)
}

$('form#issue-template-form').on('change', '.possible-values input[type=checkbox][name=checked]', function (event) {
Expand All @@ -50,16 +78,58 @@
$('form#issue-template-form').on('change', '.possible-values input[type=checkbox][name=read_only]', function (event) {
let section = event.target.closest('.split_description.select')
resetEmptyValueField(section)
})
})
$('form#issue-template-form').on('click', '.add_possible_value', function (event) {
event.preventDefault()
let ul = $(event.target).parent().find('ul')
ul.append('<%= escape_javascript(render('issue_templates/sections/select_possible_value')) %>')
toggleFieldAndLinkIconVisibilityOnAddPossibleValue($(this));
})
$('form#issue-template-form').on('keyup change', '.possible-values input.value', function (event) {

$('form#issue-template-form').on('change', 'select[id*="select_type"][class="classic-select select-type"]', function (event) {
toggleIconSpanOnChangeType($(this));
});

function toggleIconSpanOnChangeType(elem){
let divParent = elem.parent().parent();
toggleFieldAndLinkIconVisibility(divParent, elem);
}

function toggleFieldAndLinkIconVisibilityOnAddPossibleValue(elem){
let divParent = elem.parent();
// Select the neighboring select element of the parent div.which corresponds to the selected type.
// This allows for handling multiple sections of select type.
let selectType = divParent.prev('p').find('select.classic-select.select-type');
toggleFieldAndLinkIconVisibility(divParent, selectType);
}

function toggleFieldAndLinkIconVisibility(divParent, selectType) {
if (selectType.val() != "buttons_icons") {
divParent.find('.span-icons').each(function() {
$(this).hide();
});
} else {
divParent.find('.span-icons').each(function() {
$(this).show();
});
}
}

function updateFieldAndLinkIconVisibilityBasedOnSelectedType(){
let elements = document.querySelectorAll('div.sections');
elements.forEach(function( ele) {
let selectTypeList = $(ele).find('select.classic-select.select-type');
selectTypeList.each(function() {
toggleIconSpanOnChangeType($(this));
});
});
}

$('form#issue-template-form').on('keyup change', '.possible-values input.value, .possible-values input.buttons-icons', function (event) {
let ul = $(event.target).closest('ul')
resetHiddenFields(ul)
})

$('form#issue-template-form').on('click', '.remove_possible_value', function (event) {
event.preventDefault()
let ul = $(event.target).closest('ul')
Expand All @@ -72,4 +142,32 @@
$('form#issue-template-form').on('mouseleave', '.split_description', function () {
$(this).removeClass("hover")
})

// Removes empty elements at corresponding indices from the 'values' property of two objects.
function removeEmptyElementsValuesFromTwoObjects(object1, object2) {
// Split the values into arrays
object1.values = object1.values.split(";");
object2.values = object2.values.split(";");
// Arrays to store non-empty values
let newArray1 = [];
let newArray2 = [];
// Iterate over the values of the arrays
for (let i = 0; i < object1.values.length; i++) {
// Check if the values are not empty, undefined, or null in either object
if ((object1.values[i] !== "" && object1.values[i] !== undefined && object1.values[i] !== null) ||
(object2.values[i] !== "" && object2.values[i] !== undefined && object2.values[i] !== null)) {
// Add non-empty values to the new arrays
newArray1.push(object1.values[i]);
newArray2.push(object2.values[i]);
}
}
// Join the non-empty values back into strings and assign them back to the objects
object1.values = newArray1.join(";");
object2.values = newArray2.join(";");
}

$(document).ready(function() {
updateFieldAndLinkIconVisibilityBasedOnSelectedType();
});
$(document).bind('ajaxStop', updateFieldAndLinkIconVisibilityBasedOnSelectedType);
</script>
39 changes: 28 additions & 11 deletions app/views/issue_templates/sections/_select_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<% values = (form.object && form.object.text.present?) ? form.object.text.split(';') : [] %>
<% default_values = (form.object && form.object.placeholder.present?) ? form.object.placeholder.split(';') : [] %>

<% icon_names = (form.object && form.object.icon_name.present?) ? form.object.icon_name.split(';') : [] %>

<% empty_value = (form.object && form.object.empty_value.present?) ? form.object.empty_value.split(';') : [] %>
<%= issue_template_section_form(form, IssueTemplateSectionSelect, template) do %>
<p>
Expand All @@ -16,13 +18,32 @@
<p>
<%= form.text_field :description, :placeholder => "Description", label: "Description", :size => 80 %>
</p>

<p>
<%= form.select :select_type,
options_for_select(IssueTemplateSectionSelect.select_types_options,
selected: (form.object.present? ? form.object.select_type : '')),
{ label: "Type" }, { class: "classic-select select-type" } %>
<span class="span-icons">
<%= link_to 'Liste des icones compatibles', 'https://octicons.github.com', target: '_blank' %>
</span>
</p>
<div class="possible-values">
<label>Valeurs possibles<span class="required"> *</span></label>
<ul>
<% values.each do |value| %>
<%= render 'issue_templates/sections/select_possible_value', value: value, default_values: default_values, empty_value: empty_value %>
<% end %>
<% if values.length >= icon_names.length %>

<% values.each_with_index do |value, key| %>
<!-- Use an empty string if the index does not exist in icon_names -->
<% icon_name = icon_names[key] || '' %>
<%= render 'issue_templates/sections/select_possible_value', value: values[key], default_values: default_values, empty_value: empty_value, icon_name: icon_names[key] %>
<% end %>
<% else %>
<% icon_names.each_with_index do |icon_name, key| %>
<!-- Use an empty string if the index does not exist in values -->
<% value = values[key] || '' %>
<%= render 'issue_templates/sections/select_possible_value', value: value, default_values: default_values, empty_value: empty_value, icon_name: icon_name %>
<% end %>
<% end %>
</ul>
<%= link_to 'Ajouter une valeur', '#', class: 'add_possible_value' %>
</div>
Expand All @@ -32,18 +53,14 @@
<%= form.hidden_field :placeholder, class: "placeholder" %>
<!-- save read only values in the column empty_value -->
<%= form.hidden_field :empty_value, class: "empty_value", label: "", :size => 80 %>
<p>
<%= form.select :select_type,
options_for_select(IssueTemplateSectionSelect.select_types_options,
selected: (form.object.present? ? form.object.select_type : '')),
label: "Type" %>
</p>
<!-- save icons values in the column icon_name -->
<%= form.hidden_field :icon_name, class: "icon_name", label: "", :size => 80 %>

<p>
<%= form.select :display_mode,
options_for_select(IssueTemplateSection::DISPLAY_MODES.collect { |mode| [l(mode), mode] },
selected: (form.object.present? ? form.object.display_mode : '')),
label: l('display_mode') %>
{ label: l('display_mode') }, { class: "classic-select" } %>
<span><i>Option active uniquement pour les champs de type "Checkbox"</i></span>
</p>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
<% default_values = [] if default_values.blank? %>
<% empty_value = [] if empty_value.blank? %>
<% value = "" if value.blank? %>
<% icon_name = "" if icon_name.blank? %>
<li>
<span class="icon-only icon-sort-handle"></span>
<%= check_box_tag :checked, '1', default_values.include?(value) %>
<%= check_box_tag :checked, '1', default_values.include?(value) %>
<span><%= text_field_tag 'value', value, size: 45, class: 'value' %></span>

<span class="span-icons">
<span style="font-weight: bold;"><%= l(:label_icon) %></span>
<%= text_field_tag 'icon_name', icon_name, size: 25, class: 'buttons-icons' , :placeholder => l(:label_icon) %>
</span>

<%= check_box_tag :read_only, '1', empty_value.include?(value) %><span style="cursor: default;"> <%= l(:read_only) %></span>

<%= link_to 'Supprimer', '#', class: 'remove_possible_value' %>
</li>
Loading
Loading