owncloud/core

View on GitHub
core/search/js/search.js

Summary

Maintainability
D
2 days
Test Coverage
/**
 * ownCloud - core
 *
 * This file is licensed under the Affero General Public License version 3 or
 * later. See the COPYING file.
 *
 * @author Jörn Friedrich Dreyer <jfd@owncloud.com>
 * @copyright Jörn Friedrich Dreyer 2014
 */

(function () {
    /**
     * @class OCA.Search
     * @classdesc
     *
     * The Search class manages a search queries and their results
     *
     * @param $searchBox container element with existing markup for the #searchbox form
     * @param $searchResults container element for results und status message
     */
    var Search = function($searchBox, $searchResults) {
        this.initialize($searchBox, $searchResults);
    };
    /**
     * @memberof OC
     */
    Search.prototype = {

        /**
         * Initialize the search box
         *
         * @param $searchBox container element with existing markup for the #searchbox form
         * @param $searchResults container element for results und status message
         * @private
         */
        initialize: function($searchBox, $searchResults) {

            var self = this;

            /**
             * contains closures that are called to filter the current content
             */
            var filters = {};
            this.setFilter = function(type, filter) {
                filters[type] = filter;
            };
            this.hasFilter = function(type) {
                return typeof filters[type] !== 'undefined';
            };
            this.getFilter = function(type) {
                return filters[type];
            };

            /**
             * contains closures that are called to render search results
             */
            var renderers = {};
            this.setRenderer = function(type, renderer) {
                renderers[type] = renderer;
            };
            this.hasRenderer = function(type) {
                return typeof renderers[type] !== 'undefined';
            };
            this.getRenderer = function(type) {
                return renderers[type];
            };

            /**
             * contains closures that are called when a search result has been clicked
             */
            var handlers = {};
            this.setHandler = function(type, handler) {
                handlers[type] = handler;
            };
            this.hasHandler = function(type) {
                return typeof handlers[type] !== 'undefined';
            };
            this.getHandler = function(type) {
                return handlers[type];
            };

            var currentResult = -1;
            var lastQuery = '';
            var lastInApps = [];
            var lastPage = 0;
            var lastSize = 30;
            var lastResults = [];
            var timeoutID = null;
            /* For delaying search*/
            var timeout = null;                                    

            this.getLastQuery = function() {
                return lastQuery;
            };

            /**
             * Do a search query and display the results
             * @param {string} query the search query
             * @param inApps
             * @param page
             * @param size
             */
            this.search = function(query, inApps, page, size) {
                if (query) {
                    OC.addStyle('core/search','results');
                    if (typeof page !== 'number') {
                        page = 1;
                    }
                    if (typeof size !== 'number') {
                        size = 30;
                    }
                    if (typeof inApps !== 'object') {
                        var currentApp = getCurrentApp();
                        if(currentApp) {
                            inApps = [currentApp];
                        } else {
                            inApps = [];
                        }
                    }
                    // prevent double pages
                    if ($searchResults && query === lastQuery && page === lastPage && size === lastSize) {
                        return;
                    }
                    window.clearTimeout(timeoutID);
                    timeoutID = window.setTimeout(function() {
                        lastQuery = query;
                        lastInApps = inApps;
                        lastPage = page;
                        lastSize = size;

                        //show spinner
                        $searchResults.removeClass('hidden');
                        $status.addClass('status');
                        $status.html(t('core', 'Searching other places')+'<img class="spinner" alt="search in progress" src="'+OC.webroot+'/core/img/loading.gif" />');

                        // do the actual search query
                        $.getJSON(OC.generateUrl('core/search'), {query:query, inApps:inApps, page:page, size:size }, function(results) {
                            lastResults = results;
                            if (page === 1) {
                                showResults(results);
                            } else {
                                addResults(results);
                            }
                        });
                    }, 500);
                }
            };

            //TODO should be a core method, see https://github.com/owncloud/core/issues/12557
            function getCurrentApp() {
                var content = document.getElementById('content');
                if (content) {
                    var classList = document.getElementById('content').className.split(/\s+/);
                    for (var i = 0; i < classList.length; i++) {
                        if (classList[i].indexOf('app-') === 0) {
                            return classList[i].substr(4);
                        }
                    }
                }
                return false;
            }

            var $status = $searchResults.find('#status');
            // summaryAndStatusHeight is a constant
            var summaryAndStatusHeight = 118;

            function isStatusOffScreen() {
                return $searchResults.position() &&
                    ($searchResults.position().top + summaryAndStatusHeight > window.innerHeight);
            }

            function placeStatus() {
                if (isStatusOffScreen()) {
                    $status.addClass('fixed');
                } else {
                    $status.removeClass('fixed');
                }
            }
            function showResults(results) {
                lastResults = results;
                $searchResults.find('tr.result').remove();
                $searchResults.removeClass('hidden');
                addResults(results);
            }
            function addResults(results) {
                var $template = $searchResults.find('tr.template');
                jQuery.each(results, function (i, result) {
                    var $row = $template.clone();
                    $row.removeClass('template');
                    $row.addClass('result');

                    $row.data('result', result);

                    // generic results only have four attributes
                    $row.find('td.info div.name').text(result.name);
                    $row.find('td.info a').attr('href', result.link);

                    /**
                     * Give plugins the ability to customize the search results. see result.js for examples
                     */
                    if (self.hasRenderer(result.type)) {
                        $row = self.getRenderer(result.type)($row, result);
                    } else {
                        // for backward compatibility add text div
                        $row.find('td.info div.name').addClass('result');
                        $row.find('td.result div.name').after('<div class="text"></div>');
                        $row.find('td.result div.text').text(result.name);
                        if (OC.search.customResults && OC.search.customResults[result.type]) {
                            OC.search.customResults[result.type]($row, result);
                        }
                    }
                    if ($row) {
                        $searchResults.find('tbody').append($row);
                    }
                });
                var count = $searchResults.find('tr.result').length;
                $status.data('count', count);
                if (count === 0) {
                    $status.addClass('emptycontent').removeClass('status');
                    $status.html('');
                    $status.append('<div class="icon-search"></div>');
                    $status.append('<h2>' + t('core', 'No search results in other folders') + '</h2>');
                } else {
                    $status.removeClass('emptycontent').addClass('status');
                    $status.text(n('core', '{count} search result in another folder', '{count} search results in other folders', count, {count:count}));
                }
            }
            function renderCurrent() {
                var result = $searchResults.find('tr.result')[currentResult];
                if (result) {
                    var $result = $(result);
                    var currentOffset = $('#app-content').scrollTop();
                    $('#app-content').animate({
                        // Scrolling to the top of the new result
                        scrollTop: currentOffset + $result.offset().top - $result.height() * 2
                    }, {
                        duration: 100
                    });
                    $searchResults.find('tr.result.current').removeClass('current');
                    $result.addClass('current');
                }
            }
            this.hideResults = function() {
                $searchResults.addClass('hidden');
                $searchResults.find('tr.result').remove();
                lastQuery = false;
            };
            this.clear = function() {
                self.hideResults();
                if(self.hasFilter(getCurrentApp())) {
                    self.getFilter(getCurrentApp())('');
                }
                $searchBox.val('');
                $searchBox.blur();
            };

            /**
             * Event handler for when scrolling the list container.
             * This appends/renders the next page of entries when reaching the bottom.
             */
            function onScroll() {
                if ($searchResults && lastQuery !== false && lastResults.length > 0) {
                    var resultsBottom = $searchResults.offset().top + $searchResults.height();
                    var containerBottom = $searchResults.offsetParent().offset().top +
                        $searchResults.offsetParent().height();
                    if ( resultsBottom < containerBottom * 1.2 ) {
                        self.search(lastQuery, lastInApps, lastPage + 1);
                    }
                    placeStatus();
                }
            }

            $('#app-content').on('scroll', _.bind(onScroll, this));

            /**
             * scrolls the search results to the top
             */
            function scrollToResults() {
                setTimeout(function() {
                    if (isStatusOffScreen()) {
                        var newScrollTop = $('#app-content').prop('scrollHeight') - $searchResults.height();
                        console.log('scrolling to ' + newScrollTop);
                        $('#app-content').animate({
                            scrollTop: newScrollTop
                        }, {
                            duration: 100,
                            complete: function () {
                                scrollToResults();
                            }
                        });
                    }
                }, 150);
            }

            $('form.searchbox').submit(function(event) {
                event.preventDefault();
            });

            $searchBox.on('search', function () {
                if($searchBox.val() === '') {
                    if(self.hasFilter(getCurrentApp())) {
                        self.getFilter(getCurrentApp())('');
                    }
                    self.hideResults();
                }
            });
            $searchBox.keyup(function(event) {
                if (event.keyCode === 13) { //enter
                    if(currentResult > -1) {
                        var result = $searchResults.find('tr.result a')[currentResult];
                        window.location = $(result).attr('href');
                    }
                } else if(event.keyCode === 38) { //up
                    if(currentResult > 0) {
                        currentResult--;
                        renderCurrent();
                    }
                } else if(event.keyCode === 40) { //down
                    if(lastResults.length > currentResult + 1){
                        currentResult++;
                        renderCurrent();
                    }
                } else {
                    /**
                     * Search begins 500 milliseconds after the user stops typing
                     */
                    clearTimeout(timeout);
                    timeout = setTimeout(function () {
                        var query = $searchBox.val();
                        if (lastQuery !== query) {
                            currentResult = -1;
                            if (query.length > 2) {
                                self.search(query);
                            } else {
                                self.hideResults();
                            }
                            if(self.hasFilter(getCurrentApp())) {
                                self.getFilter(getCurrentApp())(query);
                            }
                        }
                        }, 500);
                }
            });
            $(document).keyup(function(event) {
                if(event.keyCode === 27) { //esc
                    $searchBox.val('');
                    if(self.hasFilter(getCurrentApp())) {
                        self.getFilter(getCurrentApp())('');
                    }
                    self.hideResults();
                }
            });

            $(document).keydown(function(event) {
                if ((event.ctrlKey || event.metaKey) && // Ctrl or Command (OSX)
                    !event.shiftKey &&
                    event.keyCode === 70 && // F
                    self.hasFilter(getCurrentApp()) && // Search is enabled
                    !$searchBox.is(':focus') // if searchbox is already focused do nothing (fallback to browser default)
                ) {
                    $searchBox.focus();
                    event.preventDefault();
                }
            });

            $searchResults.on('click', 'tr.result', function (event) {
                var $row = $(this);
                var item = $row.data('result');
                if(self.hasHandler(item.type)){
                    var result = self.getHandler(item.type)($row, item, event);
                    $searchBox.val('');
                    if(self.hasFilter(getCurrentApp())) {
                        self.getFilter(getCurrentApp())('');
                    }
                    self.hideResults();
                    return result;
                }
            });
            $searchResults.on('click', '#status', function (event) {
                event.preventDefault();
                scrollToResults();
                return false;
            });
            placeStatus();

            OC.Plugins.attach('OCA.Search', this);

            // hide search file if search is not enabled
            if(self.hasFilter(getCurrentApp())) {
                return;
            }
            if ($searchResults.length === 0) {
                $searchBox.hide();
            }
        }
    };
    OCA.Search = Search;
})();

$(document).ready(function() {
    var $searchResults = $('#searchresults');
    if ($searchResults.length > 0) {
        $searchResults.addClass('hidden');
        $('#app-content')
            .find('.viewcontainer').css('min-height', 'initial');
        $searchResults.load(OC.webroot + '/core/search/templates/part.results.html', function () {
            OC.Search = new OCA.Search($('#searchbox'), $('#searchresults'));
        });
    } else {
        _.defer(function() {
            OC.Search = new OCA.Search($('#searchbox'), $('#searchresults'));
        });
    }
});

/**
 * @deprecated use get/setRenderer() instead
 */
OC.search.customResults = {};
/**
 * @deprecated use get/setRenderer() instead
 */
OC.search.resultTypes = {};