/*! meta-client/modules/objects */
/*jslint
browser, long
*/
/*global
*/
/**
* objects module.
*
* @module meta-client/modules/objects
*/
import _ from "underscore";
import ko from "knockout";
import Logger from "js-logger";
import model from "meta-core/modules/model";
import protocol from "meta-core/modules/protocol";
import viewmodel from "meta-client/modules/viewmodel";
const LOG = Logger.get("meta-client/objects");
const newSearchQuery = function (typeId, optionalParts) {
let parts = [model.lucene.META_OBJECT_QUERY];
if (typeId) {
parts.push(model.OBJECT_TYPE_ID + ":" + typeId);
}
if (_.isArray(optionalParts)) {
parts = parts.concat(optionalParts.filter((p) => _.isEmpty(p) === false));
}
return parts.join(" AND ");
};
const factory = function ({
idProperty = model.META_ID,
loadById,
onAdd = null,
onRemove = null,
searchReplyObjectsPath = "metas",
sort = null,
toVm = null,
typeId = null,
unshift = false
}) {
const add = function (o) {
if (unshift) {
objects.objects.unshift(o);
} else {
objects.objects.push(o);
}
if (_.isFunction(onAdd)) {
ko.tasks.schedule(() => onAdd(o));
}
if (sort) {
objects.objects.sort(sort);
}
};
const handleCreate = function (id) {
LOG.debug(`handleCreate, id=${id}, typeId=${ko.unwrap(objects.typeId)}`);
if (_.isFunction(loadById)) {
return objects.loadById(id);
}
return Promise.resolve(undefined);
};
const handleDelete = function (id) {
LOG.debug(`handleDelete, id=${id}`);
return Promise.resolve(objects.removeById(id));
};
const handleUpdate = function (id, name, data) {
LOG.debug(`handleUpdate, id=${id}`);
const existing = objects.findById(id);
if (existing) {
viewmodel.updateObject(existing, name, data);
return Promise.resolve(existing);
}
return handleCreate(id);
};
const pushAll = function (o) {
ko.utils.arrayPushAll(objects.objects, (
sort
? o.sort(sort)
: o
));
};
const objects = {};
objects.typeId = typeId;
objects.isSearching = ko.observable(false);
objects.noOfSearchResults = ko.observable(0);
objects.searchTotal = ko.observable(0);
objects.searchCursor = ko.observable();
objects.canSearchMore = ko.observable(false);
objects.hasSearchTotalChanged = ko.observable(false);
objects.objects = ko.observableArray();
objects.newSearchQuery = (optionalParts) => newSearchQuery(ko.unwrap(objects.typeId), optionalParts);
if (_.isFunction(toVm)) {
objects.toVm = toVm;
} else {
objects.toVm = (o) => o;
}
objects.clear = function () {
objects.noOfSearchResults(0);
objects.searchTotal(0);
objects.searchCursor(undefined);
objects.hasSearchTotalChanged(false);
objects.objects.removeAll();
};
objects.mapSearchResults = function (searchReply) {
const searchResult = _.get(searchReply, searchReplyObjectsPath);
objects.noOfSearchResults(objects.noOfSearchResults() + searchResult.length);
return _.flatten(searchResult.map(objects.toVm));
};
objects.processSearch = function (searchReplyPromise) {
let searchExc;
objects.isSearching(true);
return searchReplyPromise.then(function (searchReply) {
const previousTotal = objects.searchTotal();
LOG.debug(`processSearch, previousTotal=${previousTotal}, searchReplyCursor=${searchReply.cursor}, searchReplyTotal=${searchReply.total}`);
objects.searchTotal(searchReply.total);
objects.searchCursor(searchReply.cursor);
objects.canSearchMore(_.isEmpty(searchReply.cursor) === false);
objects.hasSearchTotalChanged(0 < previousTotal && previousTotal !== objects.searchTotal());
pushAll(objects.mapSearchResults(searchReply));
return searchReply;
}, function (exc) {
LOG.warn("processSearch failed", exc);
searchExc = exc;
}).then(function (searchReply) {
objects.isSearching(false);
if (searchExc) {
throw searchExc;
}
return searchReply;
});
};
objects.findById = function (id) {
const predicate = {};
predicate[idProperty] = id;
return _.find(objects.objects(), predicate);
};
objects.loadById = function (id) {
LOG.debug(`loadById, id=${id}`);
return loadById(id).then(function (searchReply) {
if (searchReply) {
const searchResult = _.get(searchReply, searchReplyObjectsPath);
const o = _.first(searchResult);
if (o) {
const existing = objects.findById(id);
if (existing) {
viewmodel.updateObject(existing, o.name, o);
return existing;
}
const object = objects.toVm(o);
add(object);
return object;
}
}
});
};
objects.removeById = function (id) {
LOG.debug(`removeById, id=${id}`);
const removed = objects.objects.remove((o) => o[idProperty] === id);
if (_.isFunction(onRemove)) {
removed.forEach(onRemove);
}
};
objects.onMetaNotification = function (metaNotification) {
const metaId = metaNotification.log.metaId;
LOG.debug(`onMetaNotification, metaId=${metaId}`);
const data = metaNotification.log.data;
if (metaId && data && model.META_TYPE_OBJECT === metaNotification.log.metaType && (_.isUndefined(objects.typeId) || _.isNull(objects.typeId) || ko.unwrap(objects.typeId) === data.typeId)) {
const metaName = metaNotification.log.metaName;
switch (metaNotification.log.type) {
case protocol.WRITE_ACTION.CREATE:
return handleCreate(metaId);
case protocol.WRITE_ACTION.UPDATE:
return handleUpdate(metaId, metaName, data);
case protocol.WRITE_ACTION.DELETE:
return handleDelete(metaId);
}
}
return Promise.resolve(undefined);
};
objects[protocol.META_NOTIFICATION] = objects.onMetaNotification;
return Object.freeze(objects);
};
factory.loadByMetaId = (client, metaId) => client.search(protocol.searchRequest({limit: 1, query: metaId}));
factory.newSearchQuery = newSearchQuery;
export default Object.freeze(factory);