diff --git a/README.md b/README.md index 5fc7f58..d233a00 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # redmine_issue_dynamic_edit -Add new dropdown elements on detailed issue page to dynamically update issue's status, assignee, priority, start and due dates, ratio and estimated time fields, directly in the details block of the issue. +Add new dropdown elements on detailed issue page to dynamically update issue's status, assignee, priority, start and due dates, ratio and estimated time fields, directly in the details block of the issue without any page refresh (JIRA style). ### Example @@ -14,10 +14,12 @@ Add new dropdown elements on detailed issue page to dynamically update issue's s ### Customization Feel free to edit `assets/stylesheets/issue_dynamic_edit.css` to update the look of your fields depending on your current Redmine Theme. + This plugin uses [FontAwesome icons](http://fontawesome.io/) ### Changelog +* **v 0.4.4** : fixed Github issues #6, #12 : User can't update status until all required field are filled for this step of the issue * **v 0.4.3** : partially fixed Github issue #12 : Read only attributes can't be edited anymore. Dynamic refresh for read only attributes when status changes * **v 0.4.2** : fixed Github issue #10 : History list updated after modification * **v 0.4.1** : fixed Github issue #7 : update status list to follow Redmine workflow diff --git a/assets/javascripts/issue_dynamic_edit.js b/assets/javascripts/issue_dynamic_edit.js index 179e43f..076207a 100644 --- a/assets/javascripts/issue_dynamic_edit.js +++ b/assets/javascripts/issue_dynamic_edit.js @@ -79,7 +79,18 @@ function initEditFields() } } -initEditFields(); +initEditFields(); + +/* Add required style to attributes */ +function updateRequiredFields(reqFieldsArray) +{ + for (var i = 0; i < reqFieldsArray.length; i++) { + var htmlLabel = reqFieldsArray[i].replace(/_/g, '-'); + $('.issue.details .attribute.' + htmlLabel + ' .label').html('' + $('.issue.details .attribute.' + htmlLabel + ' .label').html() + ' *'); + } + } + +updateRequiredFields(JSON.parse($('#required_field_array').html())); $('body.controller-issues.action-show').on('click', '.btn.close', function(e){ @@ -134,32 +145,63 @@ function issueDynamicUpdate(field_name, field_value, type, cssClass){ }, success: function(msg) { /* get result page content (updated issue detail page with new status) */ + var parsed = $.parseHTML(msg); - /* we update the details block */ - $('div.issue.details').html($(parsed).find('div.issue.details').html()); - /* we init edit fields */ - initEditFields(); - initEditFieldListeners(); - - /* we update issue properties edit block */ - $('#all_attributes').html($(parsed).find('#all_attributes').html()); + var error = $(parsed).find("#errorExplanation"); - /* we update the history list */ - $('#history').append($(parsed).find('#history .journal.has-details:last-child')); - - /* data updated, remove spin and add success icon for 2sec */ - setTimeout(function(){ - $('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-check statusOk'); + if(error.length) + { + if($('html').find("#errorExplanation").length == 0) + { + $('.issue.details').before("
" + error.html() + "
"); + } else + { + $('html').find("#errorExplanation").html(error.html()); + } + /* data updated, remove spin and add success icon for 2sec */ setTimeout(function(){ - $('.details .attributes .' + cssClass + '.attribute i.fa-check.statusOk').remove(); - }, 2000); - }, 500); - - // update other fields to avoid conflict - $('#issue_lock_version').val(parseInt($('#issue_lock_version').val()) + 1 ); - $('#last_journal_id').val(parseInt($('#last_journal_id').val()) + 1 ); + $('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-times statusKo'); + setTimeout(function(){ + $('.details .attributes .' + cssClass + '.attribute i.fa-times.statusKo').remove(); + }, 2000); + }, 500); + } else { + + /* removing error div if exists */ + $('html').find("#errorExplanation").remove(); + + /* we update the details block */ + $('div.issue.details').html($(parsed).find('div.issue.details').html()); + if(type == "progress") { // specific case for progress bar + $('body').find('.details .attributes .' + cssClass + '.attribute .value').append(' '); + } else { + $('body').find('.details .attributes .' + cssClass + '.attribute .value').append(' '); + } + /* we init edit fields */ + initEditFields(); + initEditFieldListeners(); + + updateRequiredFields(JSON.parse($(parsed).find('#required_field_array').html())); + /* we update issue properties edit block */ + $('#all_attributes').html($(parsed).find('#all_attributes').html()); + + /* we update the history list */ + $('#history').append($(parsed).find('#history .journal.has-details:last-child')); + + /* data updated, remove spin and add success icon for 2sec */ + setTimeout(function(){ + $('.details .attributes .' + cssClass + '.attribute i.fa-spin').removeClass('fa-refresh fa-spin').addClass('fa-check statusOk'); + setTimeout(function(){ + $('.details .attributes .' + cssClass + '.attribute i.fa-check.statusOk').remove(); + }, 2000); + }, 500); + + // update other fields to avoid conflict + $('#issue_lock_version').val(parseInt($('#issue_lock_version').val()) + 1 ); + $('#last_journal_id').val(parseInt($('#last_journal_id').val()) + 1 ); + } }, error: function(xhr, msg, error) { /* error and no update, info logged into console */ @@ -242,7 +284,7 @@ function initEditFieldListeners() { e.preventDefault(); $('.start-date .value .error').remove(); - if(new Date(domInputStartDate.val()).getTime() <= new Date($('body').find('#DueDateInput input').val()).getTime()) + if(new Date(domInputStartDate.val()).getTime() <= new Date($('body').find('#DueDateInput input').val()).getTime() || $('body').find('#DueDateInput input').val() == "") { issueDynamicUpdate('start_date', domInputStartDate.val(), 'date', 'start-date'); } else { @@ -263,7 +305,7 @@ function initEditFieldListeners() { e.preventDefault(); $('.due-date .value .error').remove(); - if(new Date($('body').find('#StartDateInput input').val()).getTime() <= new Date(domInputDueDate.val()).getTime()) + if(new Date($('body').find('#StartDateInput input').val()).getTime() <= new Date(domInputDueDate.val()).getTime() || $('body').find('#StartDateInput input').val() == "" ) { issueDynamicUpdate('due_date', domInputDueDate.val(), 'date', 'due-date'); } else { diff --git a/config/locales/en.yml b/config/locales/en.yml index 4ddfb1b..1b0e145 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4,4 +4,5 @@ en: ide_txt_error_start_date : 'Start date must be anterior to due date' ide_txt_error_due_date : 'Due date must be posterior to start date' ide_txt_error_ajax_call : 'Error (check your JS console)' - ide_txt_cancel_btn : 'Cancel the modification' \ No newline at end of file + ide_txt_cancel_btn : 'Cancel the modification' + ide_txt_required_field : 'This field is required to go to next step' \ No newline at end of file diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 8081b32..b727ae4 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -4,4 +4,5 @@ fr: ide_txt_error_start_date : "La date de début doit être antérieure à la date d'échéance" ide_txt_error_due_date : "La date d'échéance doit être postérieure à la date de début" ide_txt_error_ajax_call : 'Erreur (logs dans la console JS)' - ide_txt_cancel_btn : 'Annuler la modification' \ No newline at end of file + ide_txt_cancel_btn : 'Annuler la modification' + ide_txt_required_field : 'Ce champs est obligatoire pour passer au Statut suivant' \ No newline at end of file diff --git a/init.rb b/init.rb index e4d898c..61b724c 100644 --- a/init.rb +++ b/init.rb @@ -5,8 +5,8 @@ require 'details_issue_hooks' Redmine::Plugin.register :redmine_issue_dynamic_edit do name 'Redmine Dynamic edit Issue plugin' author 'Hugo Zilliox' - description 'Allows users to dynamically update issue attributes in detailed view' - version '0.4.3' + description 'Allows users to dynamically update issue attributes in detailed view without refreshing the page (JIRA style)' + version '0.4.4' url 'https://github.com/ilogeek/redmine_issue_dynamic_edit' author_url 'https://hzilliox.fr' end diff --git a/lib/details_issue_hooks.rb b/lib/details_issue_hooks.rb index 5ae3b48..5bc9941 100644 --- a/lib/details_issue_hooks.rb +++ b/lib/details_issue_hooks.rb @@ -23,7 +23,16 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener if (issue_id) issue = Issue.find(issue_id) readOnlyAttributes = issue.read_only_attribute_names(User.current) - # o << issue.required_attribute_names(User.current).to_json + requiredAttributes = issue.required_attribute_names(User.current) + + # o << requiredAttributes.to_json + + allRequiredFieldsFilled = true + requiredAttributes.each do |attr| + if(issue.read_attribute(attr).to_s.empty?) + allRequiredFieldsFilled = false + end + end if (issue) if (User.current.allowed_to?(:edit_issues, project)) @@ -33,7 +42,7 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener # Status dropdown statuses = issue.new_statuses_allowed_to(User.current) - if (!statuses.empty? && !(readOnlyAttributes.include? 'status_id')) + if (!statuses.empty? && !(readOnlyAttributes.include? 'status_id') && allRequiredFieldsFilled) o << "" o << "