(function () {
var Ext = window.Ext4 || window.Ext;
/**
* @private
* A dialog that provides an easy way to bulk edit artifacts.
*
* @example
* Ext.create('Rally.ui.dialog.BulkEditDialog', {
*
* });
*/
Ext.define('Rally.ui.dialog.BulkEditDialog', {
extend: 'Rally.ui.dialog.Dialog',
alias: 'widget.rallybulkeditdialog',
requires: [
'Rally.ui.Button',
'Rally.ui.MilestoneTargetProjectPermissionsHelper',
'Rally.ui.combobox.ComboBox',
'Rally.ui.grid.FieldColumnFactory'
],
statics: {
BLACKLISTED_FIELDS_ACROSS_MODELS: ['State', 'ScheduleState']
},
autoShow: true,
cls: 'bulk-edit-dialog',
closable: true,
draggable: true,
title: 'Bulk Edit...',
width: 375,
clientMetrics: [{
event: 'beforeshow',
description: 'dialog invoked'
}, {
method: '_onFieldSelect',
description: 'edit field selected'
}, {
event: 'edit',
description: 'edit performed'
}],
config: {
/**
* @cfg {[Rally.data.Model]} records (required)
* The records to bulk edit
*/
records: null
},
initComponent: function() {
this.callParent(arguments);
this.addEvents(
/**
* @param Rally.ui.dialog.BulkEditDialog the dialog
* @param Rally.data.wsapi.Field field the field being edited
* @param {String|Number} the new value
*/
'edit'
);
this.add(
{
xtype: 'component',
cls: 'directions rui-info-label',
renderTpl: Ext.create('Ext.XTemplate',
'For the <tpl><b>{[values.recordCount]}</b></tpl> checked ',
'<tpl if="recordCount === 1">item<tpl else>items</tpl> apply the following values:'
),
renderData: {
recordCount: this.records.length
}
},
{
xtype: 'container',
itemId: 'form-container',
cls: 'form-container',
items: [
{
xtype: 'rallycombobox',
autoExpand: true,
itemId: 'editField',
store: Ext.create('Ext.data.Store', {
fields: ['name', 'displayName'],
data: this._getValidFields()
}),
defaultSelectionPosition: null,
displayField: 'displayName',
emptyText: 'Choose Field...',
valueField: 'name',
listeners: {
select: this._onFieldSelect,
scope: this
}
},
{
xtype: 'component',
cls: 'text',
html: ' to '
},
{
xtype: 'rallycombobox',
disabled: true,
itemId: 'editValue',
store: Ext.create('Ext.data.Store', {
fields: []
}),
emptyText: 'Choose Value...'
}
]
}
);
this.addDocked({
xtype: 'toolbar',
dock: 'bottom',
padding: '0 0 10 0',
layout: {
type: 'hbox',
pack: 'center'
},
ui: 'footer',
items: [
{
xtype: 'rallybutton',
itemId: 'applyButton',
text: 'Apply',
cls: 'primary rly-small',
disabled: true,
handler: this._onApplyClicked,
scope: this
},
{
xtype: 'rallybutton',
text: 'Cancel',
cls: 'secondary rly-small',
handler: function() {
this.close();
},
scope: this
}
]
});
},
afterRender: function() {
this.callParent(arguments);
this.down('#editField').focus(false, 150);
},
_onApplyClicked: function() {
var valueField = this.down('#editValue');
var args = {
field: this.records[0].self.getField(this.down('#editField').getValue()),
value: valueField.getValue()
};
if(Ext.isFunction(valueField.getRecord)) {
var record = valueField.getRecord();
args.displayValue = (record) ? record.get(valueField.displayField) : null;
} else if(Ext.isDate(valueField.getValue())) {
args.displayValue = valueField.getRawValue();
}
this.fireEvent('edit', this, args);
this.close();
},
_fieldIsBulkEditable: function(field) {
var fieldDefAttr = field.attributeDefinition;
return field.editor
&& !field.readOnly
&& !field.hidden
&& !field.isMappedFromArtifact
&& ((fieldDefAttr.AttributeType === 'OBJECT' && fieldDefAttr.Constrained) || !_.contains(['TEXT', 'COLLECTION'], fieldDefAttr.AttributeType))
&& this._fieldCanBeEditedForAllRecords(field);
},
_fieldCanBeEditedForAllRecords: function(field) {
if (field.modelType === 'milestone' && field.name === 'TargetProject') {
return _.every(this.records, function(record) {
return Rally.ui.MilestoneTargetProjectPermissionsHelper.canEdit(record);
});
}
return true;
},
_isFieldOnAllModels: function(models, field) {
return _.every(models, function(model) {
return model.hasField(field.name) &&
model.getField(field.name).attributeDefinition.AttributeType === field.attributeDefinition.AttributeType;
});
},
_isFieldBlacklistedForEditingOnAnyModel: function(models, field) {
return _.any(models, function(model) {
return Rally.ui.grid.FieldColumnFactory.isFieldBlacklistedForEditingOnGrids(field, model);
});
},
_isFieldBlacklistedForEditingAcrossModels: function(models, field) {
return models.length > 1 && _.contains(this.self.BLACKLISTED_FIELDS_ACROSS_MODELS, field.name);
},
_getValidFields: function() {
var models = _(this.records).pluck('self').unique().value();
return _(models[0].getFields())
.filter(this._fieldIsBulkEditable, this)
.filter(_.curry(this._isFieldOnAllModels)(models))
.reject(_.curry(this._isFieldBlacklistedForEditingAcrossModels.bind(this))(models))
.reject(_.curry(this._isFieldBlacklistedForEditingOnAnyModel)(models))
.sortBy('displayName')
.value();
},
_isDropdownField: function(fieldDef) {
return _.contains(fieldDef.editor.xtype, 'combobox') || _.contains(fieldDef.editor.xtype, 'rallyrecordcontexteditor');
},
_prepareDropdownEditorConfig: function(editorConfig) {
var fetch = this._getFetchForConfig(editorConfig);
var config = Ext.merge(editorConfig, {
defaultSelectionPosition: null,
editable: true,
emptyText: 'Choose Value...',
listeners: {
select: function(cmp) {
this.down('#applyButton').setDisabled(false);
},
scope: this
},
value: null
});
if (config.name === 'Project') {
config.xtype = 'rallyprojectcombobox';
} else {
config = Ext.merge(config, {
storeConfig: {
fetch: fetch,
autoLoad: true,
context: this.records[0].self.context,
requester: this
}
});
}
return config;
},
_getFetchForConfig: function(editorConfig) {
var clazz = Ext.ClassManager.getByAlias('widget.' + editorConfig.xtype);
if (clazz.prototype.config.storeConfig && clazz.prototype.config.storeConfig.fetch) {
return clazz.prototype.config.storeConfig.fetch;
} else {
return [clazz.prototype.valueField, clazz.prototype.displayField];
}
},
_prepareTextEditorConfig: function(editorConfig) {
return Ext.merge(editorConfig, {
emptyText: 'Enter Value...',
listeners: {
validitychange: function (cmp, isValid) {
this.down('#applyButton').setDisabled(!isValid);
},
scope: this
}
});
},
_prepareEditor: function(fieldDef) {
var editorConfig = fieldDef.editor;
if (editorConfig.field && editorConfig.field.xtype) {
editorConfig = editorConfig.field;
}
editorConfig = Ext.apply({
itemId: 'editValue',
width: 150,
enableKeyEvents: true,
listeners: {
keyup: function(cmp, e) {
if (!this.down('#applyButton').isDisabled() && e.getKey() === Ext.EventObject.ENTER) {
this._onApplyClicked();
}
}
}
}, editorConfig);
if (this._isDropdownField(fieldDef)) {
this.down('#applyButton').setDisabled(true);
editorConfig = this._prepareDropdownEditorConfig(editorConfig);
} else {
this.down('#applyButton').setDisabled(fieldDef.required);
editorConfig = this._prepareTextEditorConfig(editorConfig);
}
var field = this.down('#form-container').add(editorConfig);
if (Ext.isFunction(field.setProject)) {
field.setProject(this.records[0].self.context.project || Rally.environment.getContext().getProjectRef());
}
},
_onFieldSelect: function(combo, selected) {
var valueField = this.down('#editValue'),
fieldDef = this.records[0].self.getField(selected[0].get(combo.valueField));
if (valueField) {
valueField.destroy();
}
this._prepareEditor(fieldDef);
}
});
})();