newscoop/JS-Scoopwriter

View on GitHub
app/scripts/services/Topic.js

Summary

Maintainability
F
3 days
Test Coverage
'use strict';

/**
* A factory which creates an article topic model.
*
* @class Topic
*/
angular.module('authoringEnvironmentApp').factory('Topic', [
    '$http',
    '$q',
    '$timeout',
    'dateFactory',
    'pageTracker',
    'transform',
    function (
        $http,
        $q,
        $timeout,
        dateFactory,
        pageTracker,
        transform) {
        var SEARCH_DELAY_MS = 250,  // after the last search term change
            lastContext = null,  // most recent live search context
            lastTermChange = 0,  // time of the most recent search term change
            Topic = function () {};  // topic constructor

        /**
        * Converts raw data object to a Topic instance.
        *
        * @method createFromApiData
        * @param data {Object} raw object containing topic data
        * @return {Object} created Topic instance
        */
        Topic.createFromApiData = function (data) {
            var topic = new Topic();

            topic.id = parseInt(data.id);
            topic.title = data.title;
            topic.path = data.path;
            topic.parentId = parseInt(data.parent);
            topic.level = parseInt(data.level);
            topic.order = parseInt(data.order);

            // display text ... this property is expected by the select2 search
            // widget for all results
            topic.text = topic.title;

            return topic;
        };

        /**
        * Retrieves a list of all existing topics.
        *
        * Initially, an empty array is returned, which is later filled with
        * data on successful server response. At that point the given promise
        * is resolved (exposed as a $promise property of the returned array).
        *
        * @method getAll
        * @param language {String} article language code, e.g. 'de'
        * @return {Object} array of topics
        */
        Topic.getAll = function (language) {
            var topics = [],
                deferredGet = $q.defer(),
                url;

            topics.$promise = deferredGet.promise;

            url = Routing.generate(
                'newscoop_gimme_topics_gettopics',
                {language: language, items_per_page: 9999},  // de facto "all"
                true
            );

            $http.get(url)
            .success(function (response) {
                response.items.forEach(function (item) {
                    item = Topic.createFromApiData(item);
                    topics.push(item);
                });
                deferredGet.resolve();
            }).error(function (responseBody) {
                deferredGet.reject(responseBody);
            });

            return topics;
        };

        /**
        * Retrieves a list of all topics assigned to a specific article.
        *
        * Initially, an empty array is returned, which is later filled with
        * data on successful server response. At that point the given promise
        * is resolved (exposed as a $promise property of the returned array).
        *
        * @method getAllByArticle
        * @param number {Number} article ID
        * @param language {String} article language code, e.g. 'de'
        * @return {Object} array of article topics
        */
        Topic.getAllByArticle = function (number, language) {
            var topics = [],
                deferredGet = $q.defer(),
                url;

            topics.$promise = deferredGet.promise;

            url = Routing.generate(
                'newscoop_gimme_topics_getarticlestopics',
                {number: number, language: language},
                true
            );

            $http.get(url)
            .success(function (response) {
                response.items.forEach(function (item) {
                    item = Topic.createFromApiData(item);
                    topics.push(item);
                });
                deferredGet.resolve();
            }).error(function (responseBody) {
                deferredGet.reject(responseBody);
            });

            return topics;
        };

        /**
        * Retrieves a list of topics in a way that is suitable for use
        * as a query function for the select2 widget.
        *
        * @method liveSearchQuery
        * @param options {Object} options object provided by select2 on every
        *   invocation.
        * @param [isCallback=false] {Boolean} if the method is "manually"
        *   invoked (i.e. not by the select2 machinery), this flag should be
        *   set so that the method is aware of this fact
        */
        Topic.liveSearchQuery = function (options, isCallback) {
            var isPaginationCall = (options.page > 1),
                now = dateFactory.makeInstance(),
                url;

            if (!isCallback) {
                // regular select2's onType event, input changed

                if (!isPaginationCall) {
                    lastTermChange = now;

                    $timeout(function () {
                        // NOTE: tests spy on self.authorResource object, thus
                        // we don't call self.liveSearchQuery() but instead
                        // invoke the method through self.authorResource object
                        Topic.liveSearchQuery(options, true);
                    }, SEARCH_DELAY_MS);
                    return;
                } else {
                    if (angular.equals(options.context, lastContext)) {
                        // select2 bug, same pagination page called twice:
                        // https://github.com/ivaynberg/select2/issues/1610
                        return;  // just skip it
                    }
                    lastContext = options.context;
                }
            }

            if (!isPaginationCall && now - lastTermChange < SEARCH_DELAY_MS) {
                return;  // search term changed, skip this obsolete call
            }

            url = Routing.generate(
                'newscoop_gimme_topics_searchtopics',
                {
                    items_per_page: 10,
                    page: options.page,
                    query: options.term
                },
                true
            );

            $http.get(url)
            .success(function (response) {
                var topic,
                    topicList = [];

                response.items.forEach(function (item) {
                    topic = Topic.createFromApiData(item);
                    topicList.push(topic);
                });

                options.callback({
                    results: topicList,
                    more: !pageTracker.isLastPage(response.pagination),
                    context: response.pagination
                });
            });
        };

        /**
        * Creates a new topic on the server and returns a Topic instance
        * representing it.
        *
        * @method create
        * @param title {String} new topics's name
        * @param [parentId] {Number} ID of the parent topic
        * @param language {String} article language code (e.g. 'de')
        * @return {Object} promise object which is resolved with new Topic
        *   instance on success and rejected on error
        */
        Topic.create = function (title, parentId, language) {
            var deferredPost = $q.defer(),
                requestData = {topic: {}},
                url;

            requestData.topic.title = title;
            if (typeof parentId !== 'undefined') {
                requestData.topic.parent = parentId;
            }

            requestData.topic.locale = language;

            url = Routing.generate(
                'newscoop_gimme_topics_createtopic', {}, true
            );

            $http.post(
                url,
                requestData,
                {transformRequest: transform.formEncode}
            ).then(function (response) {
                var resourceUrl = response.headers('x-location');
                return $http.get(resourceUrl);  // retrieve created topic
            }, $q.reject)
            .then(function (response) {
                var topic = Topic.createFromApiData(response.data);
                deferredPost.resolve(topic);
            }, function () {
                deferredPost.reject();
            });

            return deferredPost.promise;
        };

        /**
        * Assignes all given topics to an article.
        *
        * @method addToArticle
        * @param articleId {Number} article ID
        * @param language {String} article language code (e.g. 'de')
        * @param topics {Array} list of topics to assign
        * @return {Object} promise object that is resolved on successful server
        *   response and rejected on server error response
        */
        Topic.addToArticle = function (articleId, language, topics) {
            var deferred = $q.defer(),
                linkHeader = [];

            if (topics.length < 1) {
                throw new Error('Topics list is empty.');
            }

            topics.forEach(function (item) {
                linkHeader.push(
                    '<' +
                    Routing.generate(
                        'newscoop_gimme_topics_gettopicbyid',
                        {id: item.id},
                        false
                    ) +
                    '; rel="topic">'
                );
            });
            linkHeader = linkHeader.join();

            $http({
                url: Routing.generate(
                    'newscoop_gimme_articles_linkarticle',
                    {number: articleId, language: language},
                    true
                ),
                method: 'LINK',
                headers: {link: linkHeader}
            })
            .success(function () {
                deferred.resolve(topics);
            })
            .error(function (responseBody) {
                deferred.reject(responseBody);
            });

            return deferred.promise;
        };

        /**
        * Unassignes topic from article.
        *
        * @method removeFromArticle
        * @param number {Number} article ID
        * @param language {String} article language code (e.g. 'de')
        * @return {Object} promise object that is resolved on successful server
        *   response and rejected on server error response
        */
        Topic.prototype.removeFromArticle = function(number, language) {
            var topic = this,
                deferred = $q.defer(),
                linkHeader;

            linkHeader = [
                '<',
                Routing.generate(
                    'newscoop_gimme_topics_gettopicbyid',
                    {id: topic.id},
                    false
                ),
                '; rel="topic">'
            ].join('');

            $http({
                url: Routing.generate(
                    'newscoop_gimme_articles_unlinkarticle',
                    {number: number, language:language},
                    true
                ),
                method: 'UNLINK',
                headers: {link: linkHeader}
            })
            .success(function () {
                deferred.resolve();
            })
            .error(function (responseBody) {
                deferred.reject(responseBody);
            });

            return deferred.promise;
        };

        return Topic;
    }
]);