/**
* The Base module
* a base class for all documents that have: comments, attachments, other keyvalues
* it inherits from Document to support some basic actions
* @copyright CHECKROOM NV 2015
* @module base
*/
define([
'jquery',
'common',
'api',
'document',
'comment',
'attachment',
'field'], /** @lends Base */ function ($, common, api, Document, Comment, Attachment, Field) {
// Some constant values
var DEFAULTS = {
id: "",
modified: null,
cover: null,
flag: null,
label: null,
fields: {},
comments: [],
attachments: [],
barcodes: []
};
// Allow overriding the ctor during inheritance
// http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
var tmp = function(){};
tmp.prototype = Document.prototype;
/**
* @name Base
* @class
* @property {ApiDataSource} dsAttachments attachments datasource
* @property {string} crtype e.g. cheqroom.types.customer
* @property {moment} modified last modified timestamp
* @property {string} flag the document flag
* @property {object} fields dictionary of document fields
* @property {array} comments array of Comment objects
* @property {array} attachments array of Attachment objects
* @property {string} cover cover attachment id, default null
* @constructor
* @extends Document
*/
var Base = function(opt) {
var spec = $.extend({}, opt);
Document.call(this, spec);
this.dsAttachments = spec.dsAttachments; // ApiDataSource for the attachments coll
this.crtype = spec.crtype; // e.g. cheqroom.types.customer
this.modified = spec.modified || DEFAULTS.modified; // last modified timestamp in momentjs
this.flag = spec.flag || DEFAULTS.flag; // flag
this.fields = spec.fields || $.extend({}, DEFAULTS.fields); // fields dictionary
this.comments = spec.comments || DEFAULTS.comments.slice(); // comments array
this.attachments = spec.attachments || DEFAULTS.attachments.slice(); // attachments array
this.cover = spec.cover || DEFAULTS.cover; // cover attachment id, default null
this.barcodes = spec.barcodes || DEFAULTS.barcodes.slice(); // barcodes array
this.label = spec.label || DEFAULTS.label; // color label
};
Base.prototype = new tmp();
Base.prototype.constructor = Base;
//
// Document overrides
//
Base.prototype._getDefaults = function() {
return DEFAULTS;
};
/**
* Checks if the object is empty
* after calling reset() isEmpty() should return true
* We'll only check for fields, comments, attachments here
* @name Base#isEmpty
* @method
* @returns {boolean}
* @override
*/
Base.prototype.isEmpty = function() {
return (
(this.flag==DEFAULTS.flag) &&
((this.fields==null) || (Object.keys(this.fields).length==0)) &&
((this.comments==null) || (this.comments.length==0)) &&
((this.attachments==null) || (this.attachments.length==0))
);
};
/**
* Checks if the base is dirty and needs saving
* @name Base#isDirty
* @returns {boolean}
*/
Base.prototype.isDirty = function() {
return (
(this._isDirtyFlag()) ||
(this._isDirtyFields()));
};
/**
* Checks via the api if we can delete the document
* @name Base#canDelete
* @method
* @returns {promise}
* @override
*/
Base.prototype.canDelete = function() {
// Documents can only be deleted when they have a pk
if (this.existsInDb()) {
return this.ds.call(this.id, 'canDelete');
} else {
return $.Deferred().resolve({result: false, message: ''});
}
};
// Comments
// ----
/**
* Adds a comment by string
* @name Base#addComment
* @method
* @param comment
* @param skipRead
* @returns {promise}
*/
Base.prototype.addComment = function(comment, skipRead) {
return this._doApiCall({
method: 'addComment',
params: {comment: comment},
skipRead: skipRead
});
};
/**
* Updates a comment by id
* @name Base#updateComment
* @method
* @param id
* @param comment
* @param skipRead
* @returns {promise}
*/
Base.prototype.updateComment = function(id, comment, skipRead) {
return this._doApiCall({
method: 'updateComment',
params: {commentId: id, comment: comment},
skipRead: skipRead
});
};
/**
* Deletes a Comment by id
* @name Base#deleteComment
* @method
* @param id
* @param skipRead
* @returns {promise}
*/
Base.prototype.deleteComment = function(id, skipRead) {
return this._doApiCall({
method: 'removeComment',
params: {commentId: id},
skipRead: skipRead
});
};
// Field stuff
// ----
/**
* Sets multiple custom fields in a single call
* @name Base#setFields
* @method
* @param fields
* @param skipRead
* @returns {promise}
*/
Base.prototype.setFields = function(fields, skipRead) {
var that = this,
changedFields = {};
$.each(fields, function(key, value) {
if (that.raw.fields[key]!=fields[key]) {
changedFields[key] = value;
}
});
return this._doApiCall({
method: 'setFields',
params: {fields: changedFields},
skipRead: skipRead,
usePost: true
});
};
/**
* Sets a custom field
* @name Base#setField
* @method
* @param field
* @param value
* @param skipRead
* @returns {promise}
*/
Base.prototype.setField = function(field, value, skipRead) {
return this._doApiCall({
method: 'setField',
params: {field: field, value: value},
skipRead: skipRead
});
};
/**
* Clears a custom field
* @name Base#clearField
* @method
* @param field
* @param skipRead
*/
Base.prototype.clearField = function(field, skipRead) {
return this._doApiCall({
method: 'clearField',
params: {field: field},
skipRead: skipRead
});
};
/**
* Adds a barcode
* @name Base#addBarcode
* @param code
* @param skipRead
* @returns {promise}
*/
Base.prototype.addBarcode = function(code, skipRead) {
return this._doApiCall({
method: 'addBarcode',
params: {barcode: code},
skipRead: skipRead
});
};
/**
* Removes a barcode
* @name Item#removeBarcode
* @param code
* @param skipRead
* @returns {promise}
*/
Base.prototype.removeBarcode = function(code, skipRead) {
return this._doApiCall({
method: 'removeBarcode',
params: {barcode: code},
skipRead: skipRead
});
};
// Attachments stuff
// ----
/**
* Gets an url for a user avatar
* 'XS': (64, 64),
* 'S': (128, 128),
* 'M': (256, 256),
* 'L': (512, 512)
* @param size {string} default null is original size
* @param groupId {string} Group primary key (only when you're passing an attachment)
* @param att {string} attachment primary key, by default we take the cover
* @param bustCache {boolean}
* @returns {string}
*/
Base.prototype.getImageUrl = function(size, groupId, att, bustCache) {
var attachment = att || this.cover;
return (
(attachment!=null) &&
(attachment.length>0)) ?
this.helper.getImageCDNUrl(groupId, attachment, size) :
this.helper.getImageUrl(this.ds, this.id, size, bustCache);
};
/**
* Set the cover image to an Attachment
* @name Base#setCover
* @method
* @param att
* @param skipRead
* @returns {promise}
*/
Base.prototype.setCover = function(att, skipRead) {
return this._doApiCall({
method: 'setCover',
params: {attachmentId: att._id},
skipRead: skipRead
});
};
/**
* Clears the cover image
* @name Base#clearCover
* @method
* @param skipRead
* @returns {promise}
*/
Base.prototype.clearCover = function(skipRead) {
return this._doApiCall({
method: 'clearCover',
params: {},
skipRead: skipRead
});
};
/**
* attaches an Attachment object
* @name Base#attach
* @method
* @param attachmentId
* @param skipRead
* @returns {promise}
*/
Base.prototype.attach = function(attachmentId, skipRead) {
if (this.existsInDb()) {
return this._doApiCall({
method: 'attach',
params: {attachments: [attachmentId]},
skipRead: skipRead
});
} else {
return $.Deferred().reject(new api.ApiError('Cannot attach attachment, id is empty or null'));
}
};
/**
* detaches an Attachment by kvId (guid)
* @name Base#detach
* @method
* @param attachmentId
* @param skipRead
* @returns {promise}
*/
Base.prototype.detach = function(attachmentId, skipRead) {
if (this.existsInDb()) {
return this._doApiCall({
method: 'detach',
params: {attachments: [attachmentId]},
skipRead: skipRead
});
} else {
return $.Deferred().reject(new api.ApiError('Cannot detach attachment, id is empty or null'));
}
};
// Flags stuff
// ----
/**
* Sets the flag of an item
* @name Base#setFlag
* @param flag
* @param skipRead
* @returns {promise}
*/
Base.prototype.setFlag = function(flag, skipRead) {
return this._doApiCall({
method: 'setFlag',
params: { flag: flag },
skipRead: skipRead});
};
/**
* Clears the flag of an item
* @name Base#clearFlag
* @param skipRead
* @returns {promise}
*/
Base.prototype.clearFlag = function (skipRead) {
return this._doApiCall({
method: 'clearFlag',
params: {},
skipRead: skipRead
});
};
/**
* Sets the label of an item
* @name Base#setLabel
* @param labelId
* @param skipRead
* @returns {promise}
*/
Base.prototype.setLabel = function(labelId, skipRead){
return this._doApiCall({
method: 'setLabel',
params: { labelId: labelId },
skipRead: skipRead
});
};
/**
* Clears the label of an item
* @name Base#clearLabel
* @param skipRead
* @returns {promise}
*/
Base.prototype.clearLabel = function (skipRead) {
return this._doApiCall({
method: 'clearLabel',
params: {},
skipRead: skipRead
});
};
/**
* Returns a list of Field objects
* @param fieldDefs array of field definitions
* @param onlyFormFields should return only form fields
* @param limit return no more than x fields
* @return {Array}
*/
Base.prototype.getSortedFields = function(fieldDefs, onlyFormFields, limit) {
var that = this,
fields = [],
fieldDef = null,
fieldValue = null;
// Work on copy of fieldDefs array
fieldDefs = fieldDefs.slice();
// Return only form field definitions?
fieldDefs = fieldDefs.filter(function(def) { return onlyFormFields == true ? def.form : true; });
// Create a Field object for each field definition
for (var i=0;i<fieldDefs.length;i++) {
fieldDef = fieldDefs[i];
fieldValue = that.fields[fieldDef.name] || "";
if( (limit==null) ||
(limit>fields.length)) {
fields.push(that._getField($.extend({ value: fieldValue }, fieldDef)));
}
}
return fields;
};
/**
* Update item fields based on the given Field objects
* @param {Array} fields array of Field objects
*/
Base.prototype.setSortedFields = function(fields) {
for (var i=0;i<fields.length;i++) {
var field = fields[i];
if(field.isEmpty()){
delete this.fields[field.name];
}else{
this.fields[field.name] = field.value;
}
}
};
/**
* Checks if all item fields are valid
* @param {Array} fields
* @return {Boolean}
*/
Base.prototype.validateSortedFields = function(fields) {
for (var i=0;i<fields.length;i++) {
if (!fields[i].isValid()) {
return false;
}
}
return true;
};
/**
* Update fields of a document
* @name Base#updateFields
* @returns {promise}
*/
Base.prototype.updateFields = function () {
return this._updateFields();
};
// Implementation
// ----
/**
* Checks if the flag is dirty compared to the raw response
* @returns {boolean}
* @private
*/
Base.prototype._isDirtyFlag = function() {
if (this.raw) {
return (this.flag != this.raw.flag);
} else {
return false;
}
};
/**
* Checks if the fields are dirty compared to the raw response
* @returns {boolean}
* @private
*/
Base.prototype._isDirtyFields = function() {
if (this.raw) {
return !(common.areEqual(this.fields, this.raw.fields));
} else {
return false;
}
};
/**
* Runs over the custom fields that are dirty and calls `setField`
* @returns {*}
* @private
*/
Base.prototype._updateFields = function() {
var calls = [];
if (this.raw) {
for (var key in this.fields) {
if (this.fields[key] != this.raw.fields[key]) {
calls.push(this.setField(key, this.fields[key], true));
}
}
}
if (calls.length>0) {
return $.when(calls);
} else {
return $.Deferred().resolve(this);
}
};
// toJson, fromJson
// ----
/**
* _toJson, makes a dict of params to use during create / update
* @param options
* @returns {{}}
* @private
*/
Base.prototype._toJson = function(options) {
return Document.prototype._toJson.call(this, options);
};
/**
* _fromJson: read some basic information
* @method
* @param {object} data the json response
* @param {object} options dict
* @private
*/
Base.prototype._fromJson = function(data, options) {
var that = this;
return Document.prototype._fromJson.call(this, data, options)
.then(function() {
that.flag = data.flag || DEFAULTS.flag;
that.fields = (data.fields!=null) ? $.extend({}, data.fields) : $.extend({}, DEFAULTS.fields);
that.modified = data.modified || DEFAULTS.modified;
that.barcodes = data.barcodes || DEFAULTS.barcodes;
that.label = data.label || DEFAULTS.label;
return that._fromCommentsJson(data, options)
.then(function() {
return that._fromAttachmentsJson(data, options);
});
});
};
/**
* _toJsonFields: makes json which can be used to set fields during `create`
* @method
* @param options
* @returns {{}}
* @private
*/
Base.prototype._toJsonFields = function(options) {
var fields = {};
if (this.fields) {
for (var key in this.fields) {
fields["fields__"+key] = this.fields[key];
}
}
return fields;
};
/**
* _fromCommentsJson: reads the data.comments
* @param data
* @param options
* @returns {*}
* @private
*/
Base.prototype._fromCommentsJson = function(data, options) {
var obj = null,
that = this;
this.comments = DEFAULTS.comments.slice();
if( (data.comments) &&
(data.comments.length>0)) {
$.each(data.comments, function(i, comment) {
obj = that._getComment(comment, options);
if (obj) {
that.comments.push(obj);
}
});
}
return $.Deferred().resolve(data);
};
/**
* _fromAttachmentsJson: reads the data.attachments
* @param data
* @param options
* @returns {*}
* @private
*/
Base.prototype._fromAttachmentsJson = function(data, options) {
var obj = null,
that = this;
this.attachments = DEFAULTS.attachments.slice();
if( (data.attachments) &&
(data.attachments.length>0)) {
$.each(data.attachments, function(i, att) {
obj = that._getAttachment(att, options);
if (obj) {
that.attachments.push(obj);
}
});
}
return $.Deferred().resolve(data);
};
Base.prototype._getComment = function(data, options) {
var spec = $.extend({ds: this.ds}, options || {}, data);
return new Comment(spec);
};
Base.prototype._getAttachment = function(data, options) {
var spec = $.extend({ds: this.ds}, options || {}, data);
return new Attachment(spec);
};
Base.prototype._getField = function(data, options){
var spec = $.extend({}, options || {}, data);
return new Field(spec);
};
return Base;
});