Source: objects.js

/*! 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);