@atlassian/aui
Version:
Atlassian User Interface library
321 lines (283 loc) • 9.26 kB
JavaScript
import $ from '../jquery';
import Backbone from 'backbone';
import { I18n } from '../i18n';
import classNames from './class-names';
import dataKeys from './data-keys';
import events from './event-names';
import { appendStatusSpinner, removeStatusSpinner } from './spinner';
/**
* An abstract class that gives the required behaviour for RestfulTable rows.
* Extend this class and pass it as the {views.row} property of the options passed to RestfulTable in construction.
*/
export default Backbone.View.extend({
tagName: 'tr',
events: {
'click .aui-restfultable-editable': 'edit',
},
initialize: function (options) {
options = options || {};
this._event = events;
this.classNames = classNames;
this.dataKeys = dataKeys;
this.columns = options.columns;
this.allowEdit = options.allowEdit;
this.allowDelete = options.allowDelete;
if (!this.events['click .aui-restfultable-editable']) {
throw new Error(
'It appears you have overridden the events property. To add events you will need to use' +
'a work around. https://github.com/documentcloud/backbone/issues/244'
);
}
this.index = options.index || 0;
this.deleteConfirmationCallback = options.deleteConfirmationCallback;
this.allowReorder = options.allowReorder;
this.$el = $(this.el);
this.on(this._event.CANCEL, () => (this.disabled = true))
.on(this._event.FOCUS, (field) => this.focus(field))
.on(this._event.BLUR, () => this.unfocus())
.on(this._event.MODAL, () => this.$el.addClass(this.classNames.ACTIVE))
.on(this._event.MODELESS, () => this.$el.removeClass(this.classNames.ACTIVE));
},
/**
* Renders drag handle
*
* @return jQuery
*/
renderDragHandle: function () {
return '<span class="' + this.classNames.DRAG_HANDLE + '"></span></td>';
},
/**
* Renders default cell contents
*
* @param data
*
* @return {undefiend, String}
*/
defaultColumnRenderer: function (data) {
if (data.value) {
return document.createTextNode(data.value.toString());
}
},
/**
* Save changed attributes back to server and re-render
*
* @param attr
*
* @return {Row}
*/
sync: function (attr) {
var instance = this;
this.model.addExpand(attr);
this.showLoading();
this.model.save(attr, {
success: function () {
instance.hideLoading().render();
instance.trigger(instance._event.UPDATED);
},
error: function () {
instance.hideLoading();
},
});
return this;
},
/**
* Get model from server and re-render
*
* @return {Row}
*/
refresh: function (success, error) {
var instance = this;
this.showLoading();
this.model.fetch({
success: function () {
instance.hideLoading().render();
if (success) {
success.apply(this, arguments);
}
},
error: function () {
instance.hideLoading();
if (error) {
error.apply(this, arguments);
}
},
});
return this;
},
/**
* Returns true if row has focused class
*
* @return Boolean
*/
hasFocus: function () {
return this.$el.hasClass(this.classNames.FOCUSED);
},
/**
* Adds focus class (Item has been recently updated)
*
* @return Row
*/
focus: function () {
$(this.el).addClass(this.classNames.FOCUSED);
return this;
},
/**
* Removes focus class
*
* @return Row
*/
unfocus: function () {
$(this.el).removeClass(this.classNames.FOCUSED);
return this;
},
/**
* Adds loading class (to show server activity)
*
* @return Row
*/
showLoading: function () {
appendStatusSpinner(this.$el);
return this;
},
/**
* Hides loading class (to show server activity)
*
* @return Row
*/
hideLoading: function () {
removeStatusSpinner(this.$el);
return this;
},
/**
* Switches row into edit mode
*
* @param e
*/
edit: function (e) {
var field;
if ($(e.target).is('.' + this.classNames.EDITABLE)) {
field = $(e.target).attr('data-field-name');
} else {
field = $(e.target)
.closest('.' + this.classNames.EDITABLE)
.attr('data-field-name');
}
this.trigger(this._event.ROW_EDIT, field);
return this;
},
/**
* Can be overriden to add custom options.
*
* @returns {jQuery}
*/
renderOperations: function () {
var instance = this;
if (this.allowDelete !== false) {
return $("<a href='#' class='aui-button' />")
.addClass(this.classNames.DELETE)
.text(I18n.getText('aui.words.delete'))
.on('click', function (e) {
e.preventDefault();
instance.destroy();
});
}
},
/**
* Removes entry from table.
*
* @returns {undefined}
*/
destroy: function () {
if (this.deleteConfirmationCallback) {
let promise = this.deleteConfirmationCallback(this.model.toJSON());
if (promise && promise.then) {
promise.then(
() => this.model.destroy(),
() => {}
);
} else {
throw new Error('deleteConfirmationCallback needs to return a Promise');
}
} else {
this.model.destroy();
}
},
/**
* Renders a generic edit row. You probably want to override this in a sub class.
*
* @return Row
*/
render: function () {
var instance = this;
var renderData = this.model.toJSON();
var $opsCell = $("<td class='aui-restfultable-operations' />").append(
this.renderOperations({}, renderData)
);
var $throbberCell = $(`<td class="${this.classNames.STATUS}" />`);
// restore state
this.$el
.removeClass(
this.classNames.DISABLED +
' ' +
this.classNames.FOCUSED +
' ' +
this.classNames.EDIT_ROW
)
.addClass(this.classNames.READ_ONLY)
.empty();
if (this.allowReorder) {
$('<td class="' + this.classNames.ORDER + '" />')
.append(this.renderDragHandle())
.appendTo(instance.$el);
}
this.$el.attr('data-id', this.model.id); // helper for webdriver testing
$.each(this.columns, function (i, column) {
var contents;
var $cell = $('<td />');
var value = renderData[column.id];
var fieldName = column.fieldName || column.id;
var args = [
{ name: fieldName, value: value, allowEdit: column.allowEdit },
renderData,
instance.model,
];
if (value) {
instance.$el.attr('data-' + column.id, value); // helper for webdriver testing
}
if (column.readView) {
contents = new column.readView({
model: instance.model,
}).render(args[0]);
} else {
contents = instance.defaultColumnRenderer.apply(instance, args);
}
if (instance.allowEdit !== false && column.allowEdit !== false) {
var $editableRegion = $('<span />')
.addClass(instance.classNames.EDITABLE)
.append('<span class="aui-icon aui-icon-small aui-iconfont-edit"></span>')
.append(contents)
.attr('data-field-name', fieldName);
$cell = $('<td />').append($editableRegion).appendTo(instance.$el);
if (!contents || !$.trim(contents)) {
$cell.addClass(instance.classNames.NO_VALUE);
$editableRegion.html(
$('<em />').text(this.emptyText || I18n.getText('aui.enter.value'))
);
}
} else {
$cell.append(contents);
}
if (column.styleClass) {
$cell.addClass(column.styleClass);
}
$cell.appendTo(instance.$el);
});
this.$el
.append($opsCell)
.append($throbberCell)
.addClass(this.classNames.ROW + ' ' + this.classNames.READ_ONLY);
this.trigger(this._event.RENDER, this.$el, renderData);
this.$el.trigger(this._event.CONTENT_REFRESHED, [this.$el]);
return this;
},
});