Merge pull request #906 from opf/fix/icon_link_table_comprehensibility

[FIX] Icon link table comprehensibility 4123
pull/910/head
Till Breuer 11 years ago
commit 52eb0b2cfc
  1. 54
      app/assets/javascripts/context_menu.js
  2. 28
      app/assets/javascripts/timelines/ui.js
  3. 30
      app/assets/stylesheets/content/_control_colors.css.sass
  4. 1
      app/assets/stylesheets/default.css.sass
  5. 9
      app/helpers/sort_helper.rb
  6. 6
      app/helpers/watchers_helper.rb
  7. 4
      app/views/reportings/index.html.erb
  8. 8
      app/views/work_packages/_list.html.erb
  9. 10
      config/locales/de.yml
  10. 8
      config/locales/en.yml
  11. 3
      doc/CHANGELOG.md
  12. 6
      features/reportings/reporting_administration.feature
  13. 6
      features/reportings/reporting_permissions.feature
  14. 18
      features/step_definitions/i18n_steps.rb
  15. 181
      spec/features/accessibility/work_packages/work_package_query_spec.rb

@ -232,19 +232,47 @@ ContextMenu.prototype = {
}
}
function toggleIssuesSelection(el) {
var boxes = el.getElementsBySelector('input[type=checkbox]');
var all_checked = true;
for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } }
for (i = 0; i < boxes.length; i++) {
if (all_checked) {
boxes[i].checked = false;
boxes[i].up('tr').removeClassName('context-menu-selection');
} else if (boxes[i].checked == false) {
boxes[i].checked = true;
boxes[i].up('tr').addClassName('context-menu-selection');
}
}
function isChecked(checkbox) {
return jQuery(checkbox).prop('checked') === true;
}
function setSelectionState(checkbox, select) {
var table_row = checkbox.parents('tr');
if (select) {
table_row.addClass('context-menu-selection');
} else {
table_row.removeClass('context-menu-selection');
}
};
function setAllSelectLinkState(link, all_checked) {
var span = link.find('span.hidden-for-sighted');
var state_text = I18n.t('js.button_uncheck_all');
if (all_checked) {
state_text = I18n.t('js.button_check_all');
}
link.attr('title', state_text);
link.attr('alt', state_text);
span.text(state_text);
}
function toggleSelection(link, form) {
var checkboxes = jQuery(form).find('input[type=checkbox]');
var all_checked = jQuery.makeArray(checkboxes).every(isChecked);
checkboxes.each(function(index) {
var checkbox = jQuery(this);
checkbox.prop('checked', !all_checked);
setSelectionState(checkbox, !all_checked);
setAllSelectLinkState(jQuery(link), all_checked);
});
}
function window_size() {

@ -472,6 +472,7 @@ jQuery.extend(Timeline, {
0, 0 // outline
];
var icon = '<a href="javascript://" title="%t" class="%c"/>';
var iconText = '<span class="hidden-for-sighted">';
for (i = 0; i < containers.length; i++) {
c = jQuery('<div class="tl-toolbar-container"></div>');
@ -494,15 +495,19 @@ jQuery.extend(Timeline, {
// │ Add element │
// ╰───────────────────────────────────────────────────────╯
containers[currentContainer++].append(
jQuery(icon
.replace(/%t/, timeline.i18n('timelines.new_work_package'))
.replace(/%c/, 'icon icon-add')
).click(function(e) {
e.stopPropagation();
timeline.addPlanningElement();
return false;
}));
var workPackageAddIcon = jQuery(icon
.replace(/%t/, timeline.i18n('timelines.new_work_package'))
.replace(/%c/, 'icon icon-add')
).click(function(e) {
e.stopPropagation();
timeline.addPlanningElement();
return false;
});
var workPackageAddLabel = jQuery(iconText).text(timeline.i18n('timelines.new_work_package'));
workPackageAddIcon.append(workPackageAddLabel);
containers[currentContainer++].append(workPackageAddIcon);
// ╭───────────────────────────────────────────────────────╮
// │ Spacer │
@ -549,6 +554,11 @@ jQuery.extend(Timeline, {
// top right bottom left
'margin': '4px 6px 3px'
});
var sliderHandleLabel = jQuery(iconText).text(I18n.t('js.timelines.zoom_slider'));
var sliderHandle = slider.find('a.ui-slider-handle');
sliderHandle.append(sliderHandleLabel);
containers[currentContainer + 1].append(slider);
zooms.change(function() {
slider.slider('value', this.selectedIndex + 1);

@ -0,0 +1,30 @@
/*-- copyright
* OpenProject is a project management system.
* Copyright (C) 2012-2013 the OpenProject Foundation (OPF)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 3.
*
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
* Copyright (C) 2006-2013 Jean-Philippe Lang
* Copyright (C) 2010-2013 the ChiliProject Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* See doc/COPYRIGHT.rdoc for more details. ++
*/
.delete-ctrl:before
color: red

@ -50,5 +50,6 @@
@import content/boxes
@import content/work_package_report
@import content/expandable_group_content
@import content/control_colors
@import default/main

@ -254,7 +254,14 @@ module SortHelper
caption = options.delete(:caption) || column.to_s.humanize
default_order = options.delete(:default_order) || 'asc'
lang = options.delete(:lang) || nil
options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title]
if column.to_s == @sort_criteria.first_key
options[:title] = @sort_criteria.first_asc? ? l(:label_ascending) : l(:label_descending)
options[:title] += " #{l(:label_sorted_by, "\"#{caption}\"")}"
else
options[:title] = l(:label_sort_by, "\"#{caption}\"") unless options[:title]
end
content_tag('th', sort_link(column, caption, default_order, :lang => lang), options)
end
end

@ -78,12 +78,14 @@ module WatchersHelper
avatar(watch.user, :size => "16") +
link_to_user(watch.user, :class => 'user') +
if remove_allowed
' '.html_safe + link_to(image_tag('webalys/red_x.png', :alt => l(:button_delete), :title => l(:button_delete)),
' '.html_safe + link_to(icon_wrapper('icon-context icon-close delete-ctrl',
l(:button_delete_watcher, name: watch.user.name)),
watcher_path(watch),
:method => :delete,
:remote => true,
:style => "vertical-align: middle",
:class => "delete")
:title => l(:button_delete_watcher, name: watch.user.name),
:class => "delete no-decoration-on-hover")
else
''.html_safe
end

@ -76,14 +76,14 @@ See doc/COPYRIGHT.rdoc for more details.
:class => 'icon icon-edit') do %>
<%= l(:button_edit) %>
<span class="hidden-for-sighted">
<%= I18n.t("timelines.reporting_for_project.edit", :title => reporting.reporting_to_project.name) %>
<%= I18n.t("timelines.reporting_for_project.edit_delete", :title => reporting.reporting_to_project.name) %>
</span>
<% end %>
<%= link_to_if_authorized({:action => :confirm_destroy, :project_id => @project, :id => reporting},
:class => 'icon icon-delete') do %>
<%= l(:button_delete) %>
<span class="hidden-for-sighted">
<%= I18n.t("timelines.reporting_for_project.delete", :title => reporting.reporting_to_project.name) %>
<%= I18n.t("timelines.reporting_for_project.edit_delete", :title => reporting.reporting_to_project.name) %>
</span>
<% end %>
</td>

@ -34,11 +34,11 @@ See doc/COPYRIGHT.rdoc for more details.
<thead>
<tr>
<th class="checkbox hide-when-print"><%= link_to icon_wrapper('icon-context icon-yes', "#{l(:button_check_all)}/#{l(:button_uncheck_all)}"), {},
:onclick => 'toggleIssuesSelection(Element.up(this, "form")); return false;',
<th class="checkbox hide-when-print"><%= link_to icon_wrapper('icon-context icon-yes', l(:button_check_all)), {},
:onclick => 'toggleSelection(this, jQuery(this).parents("form")); return false;',
:class => 'no-decoration-on-hover',
:title => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}",
:alt => "#{l(:button_check_all)}/#{l(:button_uncheck_all)}" %>
:title => l(:button_check_all),
:alt => l(:button_check_all) %>
</th>
<%= sort_header_tag('id', :caption => '#', :default_order => 'desc') %>

@ -340,6 +340,7 @@ de:
button_create: "Anlegen"
button_create_and_continue: "Anlegen und weiter"
button_delete: "Löschen"
button_delete_watcher: "Lösche Beobachter %{name}"
button_download: "Download"
button_duplicate: "Duplizieren"
button_edit: "Bearbeiten"
@ -588,12 +589,15 @@ de:
save: "Speichern"
start_date: "Startdatum"
really_close_dialog: "Dialog schließen und eingegebene Daten verwerfen?"
zoom_slider: "Zoom Schieber"
filter:
noneElement: "(keines)"
noneSelection: "(keine)"
label_collapse_all: "Alle zuklappen"
label_expand_all: "Alle aufklappen"
label_menu_item_toggle: "Untermenüpunkte ein-/ausblenden"
button_check_all: "Alles auswählen"
button_uncheck_all: "Alles abwählen"
label_account: "Konto"
label_activity: "Aktivität"
@ -914,7 +918,8 @@ de:
label_settings: "Konfiguration"
label_show_completed_versions: "Abgeschlossene Versionen anzeigen"
label_sort: "Sortierung"
label_sort_by: "Sortiert nach %{value}"
label_sort_by: "Sortiere nach %{value}"
label_sorted_by: "sortiert nach %{value}"
label_sort_higher: "Eins höher"
label_sort_highest: "An den Anfang"
label_sort_lower: "Eins tiefer"
@ -1545,8 +1550,7 @@ de:
reporting_could_not_be_saved: "Statusbericht konnte nicht gespeichert werden."
reporting_for_project:
show: "Statusbericht zu Projekt: %{title}"
delete: "Löschen des Statusberichts zu Projekt: %{title}"
edit: "Bearbeiten des Statusberichts zu Projekt: %{title}"
edit_delete: "des Statusberichts zu Projekt: %{title}"
history: "Historie des Statusberichts zu Projekt:"
reporting:
delete: "Statusbericht löschen: %{comment}"

@ -337,6 +337,7 @@ en:
button_create: "Create"
button_create_and_continue: "Create and continue"
button_delete: "Delete"
button_delete_watcher: "Delete watcher %{name}"
button_download: "Download"
button_duplicate: "Duplicate"
button_edit: "Edit"
@ -584,12 +585,15 @@ en:
error: "An error has occured."
create_planning_select_project: "Project:"
really_close_dialog: "Do you really want to close the dialog and loose the entered data?"
zoom_slider: "Zoom slider"
filter:
noneElement: "(none)"
noneSelection: "(none)"
label_collapse_all: "Collapse all"
label_expand_all: "Expand all"
label_menu_item_toggle: "Show/hide submenu items"
button_check_all: "Check all"
button_uncheck_all: "Uncheck all"
label_account: "Account"
label_activity: "Activity"
@ -911,6 +915,7 @@ en:
label_show_completed_versions: "Show completed versions"
label_sort: "Sort"
label_sort_by: "Sort by %{value}"
label_sorted_by: "sorted by %{value}"
label_sort_higher: "Move up"
label_sort_highest: "Move to top"
label_sort_lower: "Move down"
@ -1518,8 +1523,7 @@ en:
all: "Show all"
reporting_for_project:
show: "Status reported to project: %{title}"
delete: "Delete status reported to project: %{title}"
edit: "Edit status for project: %{title}"
edit_delete: "status report for project: %{title}"
history: "History for status for project: %{title}"
reporting:
delete: "Delete status: %{comment}"

@ -33,7 +33,8 @@ See doc/COPYRIGHT.rdoc for more details.
* `#4087` Accessible form errors
* `#4105` Remove links from fieldset
* `#4109` Missing hidden tab selection label
* `#4112` Layouttabellen: Zwei Layouttabellen erschweren das Verständnis
* `#4112` Usage of layout tables in work packages index
* `#4123` Icon link table comprehensibility
* `#4162` Missing html_safe on required list custom fields with non empty default value
## 3.0.0pre45

@ -80,7 +80,7 @@ Feature: General Reporting adminstration
Then I should see "World Domination"
And I should see "Hallo Junge"
When I follow "Edit status for project: World Domination"
When I follow link "Edit" for report "World Domination"
Then I should see "Status comment:"
And I should see "Project status:"
@ -105,7 +105,7 @@ Feature: General Reporting adminstration
And I should see "Careful Boy"
And I should see "Don't be a-gamblin'"
When I follow "Edit status for project: Careful Boy"
When I follow link "Edit" for report "Careful Boy"
And I fill in "So'n Feuerball" for "Status comment"
And I click on "Save"
@ -122,7 +122,7 @@ Feature: General Reporting adminstration
When I go to the page of the project called "Santas Project"
And I toggle the "Timelines" submenu
And I click on "Status reportings"
And I follow "Delete status reported to project: World Domination"
When I follow link "Delete" for report "World Domination"
And I click on "Delete"
Then I should see "Successful deletion."

@ -118,7 +118,7 @@ Feature: Reporting Permissions
When I go to the page of the project called "Santas Project"
And I toggle the "Timelines" submenu
And I click on "Status reportings"
And I follow "Edit status for project: World Domination"
And I follow link "Edit" for report "World Domination"
And I fill in "So'n Feuerball" for "Status comment"
And I click on "Save"
@ -145,10 +145,10 @@ Feature: Reporting Permissions
And I click on "Status reportings"
Then I should see "New reporting"
And I should see "Edit status for project: World Domination"
And I should see "Edit" for report "World Domination"
And I should not see "Delete status reported to project: World Domination"
When I follow "Edit status for project: World Domination"
When I follow link "Edit" for report "World Domination"
And I fill in "Yeah Boy" for "Status comment"
And I click on "Save"

@ -168,3 +168,21 @@ end
def locale_for_language language
{ "german" => "de", "english" => "en", "french" => "fr" }[language]
end
Then(/^I should see "(.*?)" for report "(.*?)"$/) do |link_name, table_value_name|
within 'table.timelines-reportings' do
table_data = first('td a', text: table_value_name)
row = table_data.find(:xpath, '../..')
expect(row).to have_selector('a', text: link_name)
end
end
When(/^I follow link "(.*?)" for report "(.*?)"$/) do |link_name, table_value_name|
within 'table.timelines-reportings' do
table_data = first('td a', text: table_value_name)
row = table_data.find(:xpath, '../..')
row.find('a', text: link_name).click
end
end

@ -0,0 +1,181 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2013 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
require 'features/work_packages/work_packages_page'
describe 'Work package index accessibility' do
let(:user) { FactoryGirl.create(:admin) }
let(:project) { FactoryGirl.create(:project) }
let!(:work_package) { FactoryGirl.create(:work_package,
project: project) }
let(:work_packages_page) { WorkPackagesPage.new(project) }
before do
User.stub(:current).and_return(user)
work_packages_page.visit_index
end
describe 'Select all link' do
let(:select_all_link) { find('table.list.issues th.checkbox a') }
let(:description_for_blind) { select_all_link.find('span.hidden-for-sighted') }
describe 'Initial state' do
it { expect(select_all_link).not_to be_nil }
it { expect(select_all_link[:title]).to eq(I18n.t(:button_check_all)) }
it { expect(select_all_link[:alt]).to eq(I18n.t(:button_check_all)) }
it { expect(description_for_blind.text).to eq(I18n.t(:button_check_all)) }
end
describe 'Change state', js: true do
# TODO
end
end
describe 'Sort link' do
shared_examples_for 'sort column' do
it { expect(find(sort_header_selector)[:title]).to eq(sort_text) }
end
shared_examples_for 'unsorted column' do
let(:sort_text) { I18n.t(:label_sort_by, value: "\"#{link_caption}\"") }
it_behaves_like 'sort column'
end
shared_examples_for 'ascending sorted column' do
let(:sort_text) { "#{I18n.t(:label_ascending)} #{I18n.t(:label_sorted_by, value: "\"#{link_caption}\"")}" }
it_behaves_like 'sort column'
end
shared_examples_for 'descending sorted column' do
let(:sort_text) { "#{I18n.t(:label_descending)} #{I18n.t(:label_sorted_by, value: "\"#{link_caption}\"")}" }
it_behaves_like 'sort column'
end
shared_examples_for 'descending sortable first' do
describe 'one click' do
before { find(sort_link_selector).click }
it_behaves_like 'descending sorted column'
describe 'two clicks' do
before { find(sort_link_selector).click }
it_behaves_like 'ascending sorted column'
end
end
end
shared_examples_for 'ascending sortable first' do
describe 'one click' do
before { find(sort_link_selector).click }
it_behaves_like 'ascending sorted column'
describe 'two clicks' do
before { find(sort_link_selector).click }
it_behaves_like 'descending sorted column'
end
end
end
shared_examples_for 'sortable column' do
describe 'Initial sort' do
it_behaves_like 'unsorted column'
end
end
describe 'id column' do
let(:link_caption) { '#' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
it_behaves_like 'descending sortable first'
end
describe 'type column' do
let(:link_caption) { 'Type' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
it_behaves_like 'ascending sortable first'
end
describe 'status column' do
let(:link_caption) { 'Status' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
it_behaves_like 'ascending sortable first'
end
describe 'priority column' do
let(:link_caption) { 'Priority' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
it_behaves_like 'descending sortable first'
end
describe 'subject column' do
let(:link_caption) { 'Subject' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
it_behaves_like 'ascending sortable first'
end
describe 'assigned to column' do
let(:link_caption) { 'Assignee' }
let(:sort_header_selector) { 'table.list.issues th.checkbox + th + th + th + th + th + th' }
let(:sort_link_selector) { sort_header_selector + ' a' }
it_behaves_like 'sortable column'
it_behaves_like 'ascending sortable first'
end
end
end
Loading…
Cancel
Save