Merge pull request #4078 from ulferts/feature/accessible_wp_attributes

Feature/accessible wp attributes
pull/4057/merge
Oliver Günther 9 years ago
commit 8a151e4a40
  1. 3
      config/locales/js-en.yml
  2. 1
      frontend/app/components/inplace-edit/directives/display-pane/display-pane.directive.html
  3. 4
      frontend/app/components/inplace-edit/directives/display-pane/display-pane.directive.js
  4. 11
      frontend/app/components/inplace-edit/directives/field-display/display-spent-time/display-spent-time.directive.html
  5. 3
      frontend/app/components/inplace-edit/directives/field-display/display-user/display-user.directive.html
  6. 9
      frontend/app/components/inplace-edit/directives/field-display/display-version/display-version.directive.html
  7. 30
      frontend/app/components/inplace-edit/services/work-package-field.service.js
  8. 4
      frontend/app/components/routing/views/work-packages.show.html
  9. 89
      frontend/app/components/work-packages/wp-accessible-attribute/wp-accessible-attribute.directive.test.ts
  10. 53
      frontend/app/components/work-packages/wp-accessible-attribute/wp-accessible-attribute.directive.ts
  11. 1
      frontend/app/templates/components/accessible_by_keyboard.html
  12. 3
      frontend/app/templates/inplace-edit/display/fields/boolean.html
  13. 6
      frontend/app/templates/inplace-edit/display/fields/date-range.html
  14. 3
      frontend/app/templates/inplace-edit/display/fields/embedded.html
  15. 8
      frontend/app/templates/inplace-edit/display/fields/text.html
  16. 3
      frontend/app/templates/inplace-edit/display/fields/wiki-textarea.html
  17. 3
      frontend/app/ui_components/accessible-by-keyboard-directive.js
  18. 8
      frontend/tests/integration/specs/work-packages/details-pane/date-range-picker-spec.js

@ -360,6 +360,8 @@ en:
description_options_show: "Show options"
error: "An error has occured."
error_update_failed: "The work package could not be updated."
edit_attribute: "%{attribute} - Edit"
key_value: "%{key}: %{value}"
label_enable_multi_select: "Enable multiselect"
label_disable_multi_select: "Disable multiselect"
label_filter_add: "Add filter"
@ -367,6 +369,7 @@ en:
label_column_multiselect: "Combined dropdown field: Select with arrow keys, confirm selection with enter, delete with backspace"
message_error_during_bulk_delete: An error occurred while trying to delete work packages.
message_successful_bulk_delete: Successfully deleted work packages.
no_value: "No value"
create:
header: 'New Work Package'
header_with_parent: 'New work package (Child of %{type} #%{id})'

@ -5,6 +5,7 @@
span-class="inplace-editing--container"
link-class="inplace-editing--trigger-link"
link-title="{{ fieldController.editTitle }}"
aria-label="{{ displayPaneController.getAriaLabel() }}"
execute="displayPaneController.startEditing()">
<span
class="inplace-edit--read-value"

@ -84,6 +84,10 @@ inplaceEditorDisplayPane.$inject = ['EditableFieldsState', '$timeout'];
function InplaceEditorDisplayPaneController($scope, HookService) {
var field = $scope.field;
this.getAriaLabel = function() {
return I18n.t('js.work_packages.edit_attribute', { attribute: field.getKeyValue() });
};
this.placeholder = field.placeholder;
this.startEditing = function() {

@ -1,12 +1,17 @@
<div class="spent-time-wrapper">
<span ng-if="field.isEmpty()">{{ field.placeholder }}</span>
<span ng-if="field.isEmpty()"
wp-accessible-attribute="field">
{{ field.placeholder }}
</span>
<span ng-if="!field.isEmpty()">
<span ng-if="customEditorController.isLinkViewable()">
<a href="{{ customEditorController.getPath() }}">
<a ng-href="{{ customEditorController.getPath() }}"
aria-label="{{ field.getKeyValue() }}">
{{ field.text }}
</a>
</span>
<span ng-if="!customEditorController.isLinkViewable()">
<span ng-if="!customEditorController.isLinkViewable()"
wp-accessible-attribute="field">
{{ field.text }}
</span>
</span>

@ -8,7 +8,8 @@
<span class="user-avatar--user">
<a ng-if="!user.isGroup"
ng-href="{{user.href}}"
class="user-field-user-link">
class="user-field-user-link"
aria-label="{{field.getKeyValue()}}">
{{user.name}}
</a>
<span ng-if="user.isGroup"

@ -1,13 +1,16 @@
<div class="version-wrapper">
<span ng-if="!field.text">
<span ng-if="!field.text"
wp-accessible-attribute="field">
{{field.placeholder}}
</span>
<span ng-if="field.text && customEditorController.isVersionLinkViewable()">
<a ng-href="{{ customEditorController.getVersionLink() }}">
<a ng-href="{{ customEditorController.getVersionLink() }}"
aria-label="{{ field.getKeyValue() }}">
{{field.text.props.name}}
</a>
</span>
<span ng-if="field.text && !customEditorController.isVersionLinkViewable()">
<span ng-if="field.text && !customEditorController.isVersionLinkViewable()"
wp-accessible-attribute="field">
{{field.text.props.name}}
</span>
</div>

@ -189,6 +189,35 @@ function WorkPackageFieldService($q, $http, $filter, I18n, WorkPackagesHelper,
return schema.props[field].name;
}
function getKeyValue(workPackage, field) {
var label = getLabel(workPackage, field);
var value = WorkPackageFieldService.format(workPackage, field);
if (value === null) {
value = I18n.t('js.work_packages.no_value');
}
else if (value && value.raw) {
var shortened = value.raw.length > 20;
value = $filter('limitTo')(value.raw, 20);
if (shortened) {
value += '...';
}
}
else if(value && value.props && value.props.name) {
value = value.props.name;
}
else if(value && value.props && value.props.subject) {
value = value.props.subject;
}
else if(field === 'date' && !isMilestone(workPackage)) {
value = (value.startDate || I18n.t('js.label_no_start_date')) + ' - ' +
(value.dueDate || I18n.t('js.label_no_due_date'));
}
return I18n.t('js.work_packages.key_value', { key: label, value: value });
}
function isEmpty(workPackage, field) {
if (field === 'date') {
return (
@ -394,6 +423,7 @@ function WorkPackageFieldService($q, $http, $filter, I18n, WorkPackagesHelper,
isSavedAsLink: isSavedAsLink,
getValue: getValue,
getLabel: getLabel,
getKeyValue: getKeyValue,
getAllowedValues: getAllowedValues,
allowedValuesEmbedded: allowedValuesEmbedded,
format: format,

@ -101,7 +101,9 @@
<div
ng-hide="vm.hideEmptyFields && vm.isFieldHideable(vm.workPackage, field)"
ng-if="vm.isSpecified(vm.workPackage, field)"
ng-repeat-start="field in group.attributes" class="attributes-key-value--key">
ng-repeat-start="field in group.attributes"
class="attributes-key-value--key"
id="attributes-key-value--key-{{field}}">
{{vm.getLabel(vm.workPackage, field)}}
<span class="required" ng-if="vm.hasNiceStar(vm.workPackage, field)"> *</span>
</div>

@ -0,0 +1,89 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 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.
//++
describe('wpAccessibleAttributeDirective', function() {
var html = '<div wp-accessible-attribute="field"></div>';
var scope, element, $compile;
beforeEach(angular.mock.module('openproject.workPackages.directives'));
beforeEach(inject(function(_$compile_,
$rootScope){
scope = $rootScope.$new();
$compile = _$compile_;
}));
describe('on noneditable fields', function() {
beforeEach(function() {
scope.field = {
isEditable: function() {
return false;
},
getKeyValue: function() {
return 'myKeyValue';
}
};
element = $compile(html)(scope);
scope.$apply();
});
it('has a tabindex of 0', function() {
expect(element.attr('tabindex')).to.eq('0');
});
it('has an aria-label with the keyValue', function() {
expect(element.attr('aria-label')).to.eq(scope.field.getKeyValue());
});
});
describe('on editable fields', function() {
beforeEach(function() {
scope.field = {
isEditable: function() {
return true;
},
getKeyValue: function() {
return 'myKeyValue';
}
};
element = $compile(html)(scope);
scope.$apply();
});
it('has no tabindex', function() {
expect(element.attr('tabindex')).to.eq(undefined);
});
it('has an aria-label with the keyValue', function() {
expect(element.attr('aria-label')).to.eq(undefined);
});
});
});

@ -0,0 +1,53 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 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.
//++
function wpAccessibleAttribute() {
return {
restrict: 'A',
scope: {
field: '=wpAccessibleAttribute'
},
link: function(scope, element) {
scope.$watch('field', function(field) {
if (!field.isEditable()) {
angular.element(element).attr('aria-label', field.getKeyValue())
.attr('tabindex', 0);
}
else {
angular.element(element).removeAttr('aria-label')
.removeAttr('tabindex');
}
});
}
};
}
angular
.module('openproject.workPackages.directives')
.directive('wpAccessibleAttribute', wpAccessibleAttribute);

@ -1,6 +1,7 @@
<a data-ng-click='execute()'
class='{{ linkClass }}'
title='{{ linkTitle }}'
aria-label="{{ ariaLabel }}"
data-click-on-keypress="[13, 32]">
<span ng-transclude class='{{ spanClass }}'></span>
</a>

@ -1 +1,2 @@
<span ng-bind="field.text"></span>
<span ng-bind="field.text"
wp-accessible-attribute="field"></span>

@ -1 +1,5 @@
<op-date date-value="field.text.startDate" no-date-text="field.text.noStartDate"></op-date>&nbsp;&nbsp;-&nbsp;&nbsp;<op-date date-value="field.text.dueDate" no-date-text="field.text.noEndDate"></op-date>
<span wp-accessible-attribute="field">
<op-date date-value="field.text.startDate" no-date-text="field.text.noStartDate"></op-date>
&nbsp;&nbsp;-&nbsp;&nbsp;
<op-date date-value="field.text.dueDate" no-date-text="field.text.noEndDate"></op-date>
</span>

@ -1 +1,2 @@
<span ng-bind="field.text.props.name || field.text.props.value || field.placeholder"></span>
<span wp-accessible-attribute="field"
ng-bind="field.text.props.name || field.text.props.value || field.placeholder"></span>

@ -1,2 +1,6 @@
<span ng-if="field.isEmpty()" ng-bind="field.placeholder"></span>
<span ng-if="!field.isEmpty()" ng-bind="field.text"></span>
<span ng-if="field.isEmpty()"
ng-bind="field.placeholder"
wp-accessible-attribute="field"></span>
<span ng-if="!field.isEmpty()"
ng-bind="field.text"
wp-accessible-attribute="field"></span>

@ -1 +1,2 @@
<span ng-bind-html="field.text.html || field.placeholder"></span>
<span ng-bind-html="field.text.html || field.placeholder"
wp-accessible-attribute="field"></span>

@ -34,7 +34,8 @@ module.exports = function() {
execute: '&',
linkClass: '@',
linkTitle: '@',
spanClass: '@'
spanClass: '@',
ariaLabel: '@'
},
templateUrl: '/templates/components/accessible_by_keyboard.html'
};

@ -33,7 +33,7 @@ var expect = require('../../../spec_helper.js').expect,
datepicker = detailsPaneHelper.datepicker,
elements = detailsPaneHelper.elements;
describe.only('details pane', function() {
describe('details pane', function() {
var dateRangePicker;
var normalizeString = function(string) {
@ -104,7 +104,7 @@ describe.only('details pane', function() {
var read_value = dateRangePicker.$('.inplace-edit--read-value');
var text = read_value.getText().then(function(value) { return normalizeString(value) } );
expect(text).to.eventually.equal('02/17/2015 - 04/29/2015');
expect(text).to.eventually.equal('02/17/2015 - 04/29/2015');
});
});
@ -385,7 +385,7 @@ describe.only('details pane', function() {
var read_value = dateRangePicker.$('.inplace-edit--read-value');
var text = read_value.getText().then(function(value) { return normalizeString(value) });
expect(text).to.eventually.equal('no start date - 12/27/2014');
expect(text).to.eventually.equal('no start date - 12/27/2014');
});
});
@ -450,7 +450,7 @@ describe.only('details pane', function() {
var read_value = dateRangePicker.$('.inplace-edit--read-value');
var text = read_value.getText().then(function(value) { return normalizeString(value) });
expect(text).to.eventually.equal('10/23/2014 - no end date');
expect(text).to.eventually.equal('10/23/2014 - no end date');
});
});

Loading…
Cancel
Save