/**
* The Document module
* a base class for all documents from the CHECKROOM API
* @module document
* @copyright CHECKROOM NV 2015
*/
define([
'jquery',
'common',
'api',
'colorLabel'], /** @lends Document */function ($, common, api, ColorLabel) {
// Some constant values
var DEFAULTS = {
id: ""
};
/**
* @name Document
* @class
* @constructor
* @property {ApiDataSource} ds - The documents primary key
* @property {array} _fields - The raw, unprocessed json response
* @property {string} id - The documents primary key
* @property {string} raw - The raw, unprocessed json response
*/
var Document = function(spec) {
this.raw = null; // raw json object
this.id = spec.id || DEFAULTS.id; // doc _id
this.ds = spec.ds; // ApiDataSource object
this._fields = spec._fields; // e.g. [*]
};
/**
* Resets the object
* @name Document#reset
* @method
* @returns {promise}
*/
Document.prototype.reset = function() {
// By default, reset just reads from the DEFAULTS dict again
return this._fromJson(this._getDefaults(), null);
};
/**
* Checks if the document exists in the database
* @name Document#existsInDb
* @method
* @returns {boolean}
*/
Document.prototype.existsInDb = function() {
// Check if we have a primary key
return (this.id!=null) && (this.id.length>0);
};
/**
* Checks if the object is empty
* @name Document#isEmpty
* @method
* @returns {boolean}
*/
Document.prototype.isEmpty = function() {
return true;
};
/**
* Checks if the object needs to be saved
* We don't check any of the keyvalues (or comments, attachments) here
* @name Document#isDirty
* @method
* @returns {boolean}
*/
Document.prototype.isDirty = function() {
return false;
};
/**
* Checks if the object is valid
* @name Document#isValid
* @method
* @returns {boolean}
*/
Document.prototype.isValid = function() {
return true;
};
/**
* Discards any changes made to the object from the previously loaded raw response
* or resets it when no old raw response was found
* @name Document#discardChanges
* @method
* @returns {promise}
*/
Document.prototype.discardChanges = function() {
return (this.raw) ? this._fromJson(this.raw, null) : this.reset();
};
/**
* Reloads the object from db
* @name Document#reload
* @method
* @param _fields
* @returns {promise}
*/
Document.prototype.reload = function(_fields) {
if (this.existsInDb()) {
return this.get(_fields);
} else {
return $.Deferred().reject(new api.ApiError('Cannot reload document, id is empty or null'));
}
};
/**
* Gets an object by the default api.get
* @name Document#get
* @method
* @param _fields
* @returns {promise}
*/
Document.prototype.get = function(_fields) {
if (this.existsInDb()) {
var that = this;
return this.ds.get(this.id, _fields || this._fields)
.then(function(data) {
return that._fromJson(data);
});
} else {
return $.Deferred().reject(new api.ApiError('Cannot get document, id is empty or null'));
}
};
/**
* Creates an object by the default api.create
* @name Document#create
* @method
* @param skipRead skips reading the response via _fromJson (false)
* @returns {promise}
*/
Document.prototype.create = function(skipRead) {
if (this.existsInDb()) {
return $.Deferred().reject(new Error("Cannot create document, already exists in database"));
}
if (this.isEmpty()) {
return $.Deferred().reject(new Error("Cannot create empty document"));
}
if (!this.isValid()) {
return $.Deferred().reject(new Error("Cannot create, invalid document"));
}
return this._create(skipRead);
};
/**
* Updates an object by the default api.update
* @name Document#update
* @method
* @param skipRead skips reading the response via _fromJson (false)
* @returns {promise}
*/
Document.prototype.update = function(skipRead) {
if (!this.existsInDb()) {
return $.Deferred().reject(new Error("Cannot update document without id"));
}
if (this.isEmpty()) {
return $.Deferred().reject(new Error("Cannot update to empty document"));
}
if (!this.isValid()) {
return $.Deferred().reject(new Error("Cannot update, invalid document"));
}
return this._update(skipRead);
};
/**
* Deletes an object by the default api.delete
* @name Document#delete
* @method
* @returns {promise}
*/
Document.prototype.delete = function() {
// Call the api /delete on this document
if (this.existsInDb()) {
return this._delete();
} else {
return $.Deferred().reject(new Error("Document does not exist"));
}
};
// toJson, fromJson
// ----
Document.prototype._getDefaults = function() {
return DEFAULTS;
};
/**
* _toJson, makes a dict of this object
* Possibly inheriting classes will override this method,
* because not all fields can be set during create / update
* @method
* @param options
* @returns {{}}
* @private
*/
Document.prototype._toJson = function(options) {
return {
id: this.id
};
};
/**
* _fromJson: in this implementation we'll only read
* the data.keyValues into: comments, attachments, keyValues
* @method
* @param {object} data the json response
* @param {object} options dict
* @private
*/
Document.prototype._fromJson = function(data, options) {
this.raw = data;
this.id = data._id || DEFAULTS.id;
return $.Deferred().resolve(data);
};
// Implementation stuff
// ---
/**
* The actual _create implementation (after all the checks are done)
* @param skipRead
* @returns {*}
* @private
*/
Document.prototype._create = function(skipRead) {
var that = this;
var data = this._toJson();
delete data.id;
return this.ds.create(data, this._fields)
.then(function(data) {
return (skipRead==true) ? data : that._fromJson(data);
});
};
/**
* The actual _update implementation (after all the checks are done)
* @param skipRead
* @returns {*}
* @private
*/
Document.prototype._update = function(skipRead) {
var that = this;
var data = this._toJson();
delete data.id;
return this.ds.update(this.id, data, this._fields)
.then(function(data) {
return (skipRead==true) ? data : that._fromJson(data);
});
};
/**
* The actual _delete implementation (after all the checks are done)
* @returns {*}
* @private
*/
Document.prototype._delete = function() {
var that = this;
return this.ds.delete(this.id)
.then(function() {
return that.reset();
});
};
/**
* Helper for checking if a simple object property is dirty
* compared to the original raw result
* @param prop
* @returns {boolean}
* @private
*/
Document.prototype._isDirtyProperty = function(prop) {
return (this.raw) ? (this[prop]!=this.raw[prop]) : false;
};
/**
* Helper for checking if a simple object property is dirty
* compared to the original raw result
* Because we know that the API doesn't return empty string properties,
* we do a special, extra check on that.
* @param prop
* @returns {boolean}
* @private
*/
Document.prototype._isDirtyStringProperty = function(prop) {
if (this.raw) {
var same = (this[prop]==this.raw[prop]) || ((this[prop]=="") && (this.raw[prop]==null));
return !same;
} else {
return false;
}
};
/**
* Helper for checking if a simple object property is dirty
* compared to the original raw result
* @param prop
* @returns {boolean}
* @private
*/
Document.prototype._isDirtyMomentProperty = function(prop) {
if (this.raw) {
var newVal = this[prop],
oldVal = this.raw[prop];
if (newVal==null && oldVal==null) {
return false;
} else if (newVal && oldVal) {
return !newVal.isSame(oldVal);
} else {
return true;
}
} else {
return false;
}
};
/**
* Gets the id of a document
* @param obj
* @param prop
* @returns {string}
* @private
*/
Document.prototype._getId = function(obj, prop) {
return (typeof obj === 'string') ? obj : obj[prop || "_id"];
};
Document.prototype._getIds = function(objs, prop) {
return objs.map(function(obj){
return typeof(obj) == "string"? obj: obj[prop || "_id"];
});
};
/**
* Wrapping the this.ds.call method
* {pk: '', method: '', params: {}, _fields: '', timeOut: null, usePost: null, skipRead: null}
* @method
* @param spec
* @returns {promise}
* @private
*/
Document.prototype._doApiCall = function(spec) {
var that = this;
return this.ds.call(
(spec.collectionCall==true) ? null : (spec.pk || this.id),
spec.method,
spec.params,
spec._fields || this._fields,
spec.timeOut,
spec.usePost)
.then(function(data) {
return (spec.skipRead==true) ? data : that._fromJson(data);
});
};
/**
* Wrapping the this.ds.call method with a longer timeout
* {pk: '', method: '', params: {}, _fields: '', timeOut: null, usePost: null, skipRead: null}
* @method
* @param spec
* @returns {promise}
* @private
*/
Document.prototype._doApiLongCall = function(spec) {
spec.timeOut = spec.timeOut || 30000;
return this._doApiCall(spec);
};
Document.prototype._getColorLabel = function(data, options) {
var spec = $.extend({}, options || {}, data);
return new ColorLabel(spec);
};
return Document;
});