@atlassian/aui
Version:
Atlassian User Interface library
207 lines (179 loc) • 6.22 kB
JavaScript
import $ from '../jquery';
import { triggerEvtForInst } from './event-handlers';
import { isEmpty, isEqual } from 'underscore';
import Backbone from 'backbone';
import events from './event-names';
/**
* A class provided to fill some gaps with the out of the box Backbone.Model class. Most notiably the inability
* to send ONLY modified attributes back to the server.
*/
var EntryModel = Backbone.Model.extend({
sync: function (method, model, options) {
var instance = this;
var oldError = options.error;
options.error = function (xhr) {
instance._serverErrorHandler(xhr, this);
if (oldError) {
oldError.apply(this, arguments);
}
};
return Backbone.sync.apply(Backbone, arguments);
},
/**
* Overrides default save handler to only save (send to server) attributes that have changed.
* Also provides some default error handling.
*
* @override
* @param attributes
* @param options
*/
save: function (attributes, options) {
options = options || {};
var instance = this;
var Model;
var syncModel;
var error = options.error; // we override, so store original
var success = options.success;
// override error handler to provide some defaults
options.error = function (model, xhr) {
var data = $.parseJSON(xhr.responseText || xhr.data);
// call original error handler
if (error) {
error.call(instance, instance, data, xhr);
}
};
// if it is a new model, we don't have to worry about updating only changed attributes because they are all new
if (this.isNew()) {
// call super
Backbone.Model.prototype.save.call(this, attributes, options);
// only go to server if something has changed
} else if (attributes) {
// create temporary model
Model = EntryModel.extend({
url: this.url(),
});
syncModel = new Model({
id: this.id,
});
syncModel.save = Backbone.Model.prototype.save;
options.success = function (model, xhr) {
// update original model with saved attributes
instance.clear().set(model.toJSON());
// call original success handler
if (success) {
success.call(instance, instance, xhr);
}
};
// update temporary model with the changed attributes
syncModel.save(attributes, options);
}
},
/**
* Destroys the model on the server. We need to override the default method as it does not support sending of
* query paramaters.
*
* @override
* @param {object} [options]
* @param {function} options.success - Server success callback
* @param {function} options.error - Server error callback
* @param {object} options.data
*
* @return EntryModel
*/
destroy: function (options) {
options = options || {};
var instance = this;
var url = this.url();
$.ajax({
url: url,
type: 'DELETE',
dataType: 'json',
data: options.data || {},
contentType: 'application/json',
success(data) {
if (instance.collection) {
instance.collection.remove(instance);
}
if (options.success) {
options.success.call(instance, data);
}
},
error(xhr) {
instance._serverErrorHandler(xhr, this);
if (options.error) {
options.error.call(instance, xhr);
}
},
});
return this;
},
/**
* A more complex lookup for changed attributes then default backbone one.
*
* @param attributes
*/
changedAttributes: function (attributes) {
var changed = {};
var current = this.toJSON();
$.each(attributes, function (name, value) {
if (!current[name]) {
if (typeof value === 'string') {
if ($.trim(value) !== '') {
changed[name] = value;
}
} else if ($.isArray(value)) {
if (value.length !== 0) {
changed[name] = value;
}
} else {
changed[name] = value;
}
} else if (current[name] && current[name] !== value) {
if (typeof value === 'object') {
if (!isEqual(value, current[name])) {
changed[name] = value;
}
} else {
changed[name] = value;
}
}
});
if (!isEmpty(changed)) {
this.addExpand(changed);
return changed;
}
},
/**
* Useful point to override if you always want to add an expand to your rest calls.
*
* @param changed attributes that have already changed
*/
addExpand: function (changed) {}, // eslint-disable-line no-unused-vars
/**
* Throws a server error event unless user input validation error (status 400)
*
* @param xhr
* @param ajaxOptions
*/
_serverErrorHandler: function (xhr, ajaxOptions) {
var data;
if (xhr.status !== 400) {
data = $.parseJSON(xhr.responseText || xhr.data);
triggerEvtForInst(events.SERVER_ERROR, this, [data, xhr, ajaxOptions]);
}
},
/**
* Fetches values, with some generic error handling
*
* @override
* @param options
*/
fetch: function (options) {
options = options || {};
// clear the model, so we do not merge the old with the new
this.clear();
// call super
Backbone.Model.prototype.fetch.call(this, options);
},
});
export default EntryModel;