Merge pull request #1021 from opf/feature/ui-components-timeline-custom-fields

Feature/ui components timeline custom fields
pull/1040/head
manwithtwowatches 11 years ago
commit bbb9deb9d3
  1. 17
      app/assets/javascripts/angular/directives/timelines/option_column_directive.js
  2. 71
      app/assets/javascripts/angular/directives/timelines/timeline-column-directive.js
  3. 25
      app/assets/javascripts/angular/directives/timelines/timeline-column-name-directive.js
  4. 78
      app/assets/javascripts/angular/filters/column_filters.js
  5. 36
      app/assets/javascripts/angular/helpers/components/custom-field-helper.js
  6. 9
      app/assets/javascripts/angular/models/timelines/timeline.js
  7. 2
      config/locales/de.yml
  8. 2
      config/locales/en.yml
  9. 2
      config/locales/js-de.yml
  10. 2
      config/locales/js-en.yml
  11. 2
      features/step_definitions/timelines_then_steps.rb
  12. 18
      public/templates/timelines/timeline_column.html
  13. 34
      public/templates/timelines/timeline_table.html

@ -1,17 +0,0 @@
angular.module('openproject.timelines.directives')
.directive('optionColumn', [function() {
return {
restrict: 'A',
scope: true,
compile: function(tElement, tAttrs, transclude) {
return {
pre: function(scope, iElement, iAttrs, controller) {
scope.isDateOption = function(option) {
return (option === 'start_date' || option === 'due_date');
};
}
};
}
};
}]);

@ -0,0 +1,71 @@
angular.module('openproject.timelines.directives')
.constant('WORK_PACKAGE_DATE_COLUMNS', ['start_date', 'due_date'])
.directive('timelineColumn', ['WORK_PACKAGE_DATE_COLUMNS', 'I18n', 'CustomFieldHelper', function(WORK_PACKAGE_DATE_COLUMNS, I18n, CustomFieldHelper) {
return {
restrict: 'A',
scope: {
rowObject: '=',
columnName: '=',
timeline: '=',
customFields: '='
},
templateUrl: '/templates/timelines/timeline_column.html',
link: function(scope, element) {
scope.isDateColumn = WORK_PACKAGE_DATE_COLUMNS.indexOf(scope.columnName) !== -1;
scope.historicalDateKind = getHistoricalDateKind(scope.rowObject, scope.columnName);
if (CustomFieldHelper.isCustomFieldKey(scope.columnName)) {
// watch custom field because they are loaded after the rows are being iterated
scope.$watch('timeline.custom_fields', function() {
scope.columnData = getCustomFieldColumnData(scope.rowObject, scope.columnName, scope.customFields, scope.timeline.users);
});
} else {
scope.columnData = getColumnData();
}
function getHistoricalDateKind(object, value) {
if (!object.does_historical_differ()) return;
var newDate = object[value];
var oldDate = object.historical()[value];
if (oldDate && newDate) {
return (newDate < oldDate ? 'postponed' : 'preponed');
}
return "changed";
}
function getColumnData() {
var map = {
"type": "getTypeName",
"status": "getStatusName",
"responsible": "getResponsibleName",
"assigned_to": "getAssignedName",
"project": "getProjectName"
};
switch(scope.columnName) {
case 'start_date':
return scope.rowObject.start_date;
case 'due_date':
return scope.rowObject.due_date;
default:
return scope.rowObject[map[scope.columnName]]();
}
}
function getCustomFieldColumnData(object, customFieldName, customFields, users) {
if(!customFields) return; // custom_fields provides necessary meta information about the custom field column
var customField = customFields[CustomFieldHelper.getCustomFieldId(customFieldName)];
if (customField) {
return CustomFieldHelper.formatCustomFieldValue(object[customFieldName], customField.field_format, users);
}
}
}
};
}]);

@ -0,0 +1,25 @@
angular.module('openproject.timelines.directives')
.directive('timelineColumnName', ['I18n', 'CustomFieldHelper', function(I18n, CustomFieldHelper) {
return {
restrict: 'A',
scope: {
columnName: '=',
customFields: '=',
localePrefix: '@'
},
link: function(scope, element) {
if (CustomFieldHelper.isCustomFieldKey(scope.columnName)) {
scope.$watch('customFields', function(){
var customFieldId = CustomFieldHelper.getCustomFieldId(scope.columnName);
if (scope.customFields && scope.customFields[customFieldId]) {
element.html(scope.customFields[customFieldId].name);
}
});
} else {
element.html(I18n.t(scope.localePrefix + '.' + scope.columnName));
}
}
};
}]);

@ -1,78 +0,0 @@
angular.module('openproject.uiComponents')
.filter('historicalDateKind', function() {
return function(object, dateOption) {
if (!object.does_historical_differ()) return;
var newDate = object[dateOption];
var oldDate = object.historical()[dateOption];
if (oldDate && newDate) {
return (newDate < oldDate ? 'postponed' : 'preponed');
}
return "changed";
};
})
// timelines
.filter('getOptionColumn', function() {
var map = {
"type": "getTypeName",
"status": "getStatusName",
"responsible": "getResponsibleName",
"assigned_to": "getAssignedName",
"project": "getProjectName"
};
return function(object, option) {
switch(option) {
case 'start_date':
return object.start_date;
case 'due_date':
return object.due_date;
default:
return object[map[option]]();
}
};
});
// TODO integrate custom field columns as can be seen in the example provided by the code copied from ui.js
// ...
// function booleanCustomFieldValue(value) {
// if (value) {
// if (value === "1") {
// return timeline.i18n("general_text_Yes")
// } else if (value === "0") {
// return timeline.i18n("general_text_No")
// }
// }
// }
// function formatCustomFieldValue(value, custom_field_id) {
// switch(timeline.custom_fields[custom_field_id].field_format) {
// case "bool":
// return booleanCustomFieldValue(value);
// case "user":
// if (timeline.users[value])
// return timeline.users[value].name;
// default:
// return value;
// }
// }
// function getCustomFieldValue(data, custom_field_name) {
// var custom_field_id = parseInt(custom_field_name.substr(3), 10), value = data[custom_field_name];
// if (value) {
// return jQuery('<span class="tl-column">' + timeline.escape(formatCustomFieldValue(value, custom_field_id)) + '</span>');
// }
// }
// var timeline = this;
// return {
// all: ['due_date', 'type', 'status', 'responsible', 'start_date'],
// general: function (data, val) {
// if (val.substr(0, 3) === "cf_") {
// return getCustomFieldValue(data, val);
// }
// ...

@ -0,0 +1,36 @@
angular.module('openproject.uiComponents')
.constant('CUSTOM_FIELD_PREFIX', 'cf_')
.service('CustomFieldHelper', ['CUSTOM_FIELD_PREFIX', 'I18n', function(CUSTOM_FIELD_PREFIX, I18n) {
CustomFieldHelper = {
isCustomFieldKey: function(key) {
return key.substr(0, CUSTOM_FIELD_PREFIX.length) === CUSTOM_FIELD_PREFIX;
},
getCustomFieldId: function(cfKey) {
return parseInt(cfKey.substr(CUSTOM_FIELD_PREFIX.length, 10), 10);
},
booleanCustomFieldValue: function(value) {
if (value) {
if (value === '1') {
return I18n.t('js.general_text_Yes');
} else if (value === '0') {
return I18n.t('js.general_text_No');
}
}
},
formatCustomFieldValue: function(value, fieldFormat, users) {
switch(fieldFormat) {
case 'bool':
return CustomFieldHelper.booleanCustomFieldValue(value);
case 'user':
if (users[value])
return users[value].name;
break;
default:
return value;
}
}
};
return CustomFieldHelper;
}]);

@ -1,7 +1,7 @@
angular.module('openproject.timelines.models')
.factory('Timeline', ['Constants', 'TreeNode', 'UI', 'Color', 'HistoricalPlanningElement', 'PlanningElement', 'PlanningElementType', 'ProjectType', 'Project', 'ProjectAssociation', 'Reporting', 'CustomField', function(Constants, TreeNode, UI, Color, HistoricalPlanningElement, PlanningElement, PlanningElementType, ProjectType, Project, ProjectAssociation, Reporting, CustomField) {
.factory('Timeline', ['Constants', 'TreeNode', 'UI', 'Color', 'HistoricalPlanningElement', 'PlanningElement', 'PlanningElementType', 'ProjectType', 'Project', 'ProjectAssociation', 'Reporting', 'CustomField', 'CustomFieldHelper', function(Constants, TreeNode, UI, Color, HistoricalPlanningElement, PlanningElement, PlanningElementType, ProjectType, Project, ProjectAssociation, Reporting, CustomField, CustomFieldHelper) {
Timeline = {};
@ -399,7 +399,7 @@ angular.module('openproject.timelines.models')
},
getCustomFieldColumns: function() {
return this.options.columns.filter(function(column) {
return column.substr(0, 3) === 'cf_';
return CustomFieldHelper.isCustomFieldKey(column);
});
},
getCustomFields: function() {
@ -416,15 +416,12 @@ angular.module('openproject.timelines.models')
return cf.id;
});
},
getIdOfCustomFieldColumn: function(cfColumn) {
return parseInt(cfColumn.substr(3, 10), 10);
},
getInvalidCustomFieldColumns: function() {
var validCustomFieldIds = this.getValidCustomFieldIds();
var timeline = this;
return this.getCustomFieldColumns().filter(function(cfColumn) {
return validCustomFieldIds.indexOf(timeline.getIdOfCustomFieldColumn(cfColumn)) === -1;
return validCustomFieldIds.indexOf(CustomFieldHelper.getCustomFieldId(cfColumn)) === -1;
});
},
removeColumnByName: function(columnName) {

@ -547,6 +547,8 @@ de:
general_pdf_encoding: "ISO-8859-1"
general_text_no: "nein"
general_text_yes: "ja"
general_text_No: "Nein"
general_text_Yes: "Ja"
gui_validation_error: "1 Fehler"
gui_validation_error_plural: "%{count} Fehler"

@ -542,6 +542,8 @@ en:
general_pdf_encoding: "ISO-8859-1"
general_text_no: "no"
general_text_yes: "yes"
general_text_No: "No"
general_text_Yes: "Yes"
gui_validation_error: "1 error"
gui_validation_error_plural: "%{count} errors"

@ -13,6 +13,8 @@ de:
noneElement: "(keines)"
general_text_no: "nein"
general_text_yes: "ja"
general_text_No: "Nein"
general_text_Yes: "Ja"
label_add_columns: "Ausgewählte Spalten hinzufügen"
label_all_work_packages: "alle Arbeitspakete"
label_collapse: "Zuklappen"

@ -13,6 +13,8 @@ en:
noneElement: "(none)"
general_text_no: "no"
general_text_yes: "yes"
general_text_No: "No"
general_text_Yes: "Yes"
label_add_columns: "Add selected columns"
label_all_work_packages: "all work packages"
label_collapse: "Collapse"

@ -86,7 +86,7 @@ end
Then(/^I should see "(.*?)" in the row of the work package "(.*?)"$/) do |content, wp_name|
elements = find_lowest_containing_element wp_name, ".tl-main-table"
elements[-1].should have_xpath("ancestor::tr/descendant-or-self::*[text()='#{content}']")
elements[-1].should have_xpath("ancestor::tr/descendant-or-self::*[contains(text(), '#{content}')]")
end
Then(/^I should not see "(.*?)" in the row of the work package "(.*?)"$/) do |content, wp_name|

@ -0,0 +1,18 @@
<span class="tl-historical" ng-show="rowObject.does_historical_differ(columnName)">
<a href="javascript://" title="{{I18n.t('js.timelines.change')}}"
ng-class="[
'icon',
'tl-icon-' + isDateColumn && historicalDateKind || 'tl-icon-changed'
]">
{{rowObject.historical()[columnName] || I18n.t('js.timelines.empty')}}
</a>
<br/>
</span>
<span ng-class="[
'tl-column',
isDateColumn && 'tl-current',
isDateColumn && rowObject.does_historical_differ(columnName) && 'tl-' + (historicalDateKind)
]">
{{columnData}}
</span>

@ -2,7 +2,11 @@
<thead>
<tr>
<th class="tl-first-column" ng-style="{height: height}">{{I18n.t('js.timelines.filter.column.name')}}</th>
<th ng-repeat="key in columns">{{I18n.t('js.timelines.filter.column.' + key)}}</th>
<th timeline-column-name
ng-repeat="columnName in columns"
column-name="columnName"
locale-prefix="js.timelines.filter.column"
custom-fields="timeline.custom_fields"></th>
</tr>
</thead>
@ -26,6 +30,8 @@
ng-if="!(hideTreeRoot && $first || rowObjectType === 'Project' && excludeEmpty && !row.childNodes)"
ng-show="rowObjectType === 'Project' && row.level === 1 || (row.ancestors | ancestorsExpanded)">
<!-- tree node expansion toggle -->
<td ng-class="[
'tl-first-column',
'tl-indent-' + indent,
@ -49,26 +55,12 @@
</span>
</td>
<td option-column
ng-repeat="option in columns">
<span class="tl-historical" ng-show="rowObject.does_historical_differ(option)">
<a href="javascript://" title="{{I18n.t('js.timelines.change')}}"
ng-class="[
'icon',
'tl-icon-' + isDateOption(option) && (rowObject | historicalDateKind:option) || 'tl-icon-changed'
]">
{{rowObject.historical()[option] || I18n.t('js.timelines.empty')}}
</a>
<br/>
</span>
<span ng-class="[
'tl-column',
isDateOption(option) && 'tl-current',
isDateOption(option) && rowObject.does_historical_differ(option) && 'tl-' + (rowObject | historicalDateKind:option)
]">
{{rowObject | getOptionColumn:option}}
</span>
<td timeline-column
ng-repeat="columnName in columns"
column-name="columnName"
row-object="rowObject"
timeline="timeline"
custom-fields="timeline.custom_fields">
</td>
</tr>
</tbody>

Loading…
Cancel
Save