Facets.js 14.3 KB
define(function(require) {
    'use strict';
    var _ = require('underscore');
    var Backbone = require('backbone');
    var NestedModels = require('backbone-nested-models');
    var Facet = require('./Facet');
    var Sort = require('./Sort');

    function getField(obj, key) {
        return obj[key];
    }

    function getItemKeyAccessor(item) {
        return item.get('key');
    }

    var Facets =  NestedModels.mixin(Backbone.Model.extend({
        initialize: function(data, options) {
            this.url = function() {
                var url = options.search.url;
                return _.isFunction(url) ? url.call() : url;
            };
            _.each(this.keys(), _.bind(function(facetName) {
                var facet = this.get(facetName);
                _.each({
                    'item-change': 'item-change',
                    'change:query': 'child-query',
                    'change:queryMin': 'item-change',
                    'change:queryMax': 'item-change',
                }, function(eventValue, eventKey) {
                    facet.on(eventKey, function(model, value, options) {
                        console.log('trigger', facet.get('formName'), eventValue);
                        this.trigger(eventValue, facet, null, options);
                    }, this);
                }, this);
            }, this));

            this._doSuggestions = _.debounce(_.bind(function(childFacet) {
                if (childFacet.get('query')) {
                    this.fetch({
                        data: this.toJSON({childFacet: childFacet, facetSuggestions: true}),
                        childFacet: childFacet,
                        facetSuggestions: true,
                        merge: true,
                        traditional: true,
                    });
                } else {
                    childFacet.set('suggestions', []);
                }
            }, this), 250);
            this.on('child-query', this._doSuggestions, this);

            return Facets.__super__.initialize.apply(this, arguments);
        },
        resetSearch: function(options) {
            _.each(this.values(), function(facet) {
                facet.get('items').reset(null, options);
            });
        },
        applyFacetResults: function(data, options) {
            options = options || {};
            var facetCounts = getField(data, 'facet_counts');
            var facetIntervals = getField(facetCounts, 'facet_intervals');
            var facetRanges = getField(facetCounts, 'facet_ranges');
            var facetFields = getField(facetCounts, 'facet_fields');
            var facetQueries = getField(facetCounts, 'facet_queries');
            var statsFields = getField(data.stats, 'stats_fields');
            function getFacetList(facetName, type) {
                switch (type) {
                    case 'year':
                    case 'range':
                        return facetRanges[facetName] ? facetRanges[facetName].counts : null;
                    case 'interval':
                        return facetIntervals[facetName] ? facetIntervals[facetName].counts : null;
                    case 'year-field':
                    case 'field':
                        return facetFields[facetName];
                }
            }
            _.each(this.keys(), _.bind(function(facetName) {
                var facet = this.get(facetName);
                var type = facet.facetType;

                var suggestions = [];
                _.each(getFacetList('suggestions:' + facetName, type), function(value, index) {
                    if (index % 2 === 0) {
                        key = value;
                    } else if (value > 0) {
                        suggestions.push({key: key, value: value});
                    }
                });
                facet.set('suggestions', suggestions);
                //if (!!options.facetSuggestions && options.childFacet === facet) {
                if (!!options.facetSuggestions) {
                    return;
                }

                var key;
                var list = getFacetList(facetName, type);
                var newItems = [];
                var items = facet.get('items');
                items.invoke('set', 'hidden', true);
                var valueOverrides = {};
                var excludeValues = {};
                function recordIncludeValueIntoExclude(includeValue, key) {
                    excludeValues[includeValue] = true;
                }
                switch (type) {
                    case 'year-field':
                    case 'field':
                        _.each(facet.bins, function(includeValues, key) {
                            var tag = facetName + ':' + key;
                            valueOverrides[key] = facetQueries[tag];
                            _.each(includeValues, recordIncludeValueIntoExclude);
                        });
                        break;
                }
                var addNewItem = _.bind(function addNewItem(key, value) {
                    if (valueOverrides[key] !== undefined) {
                        value = valueOverrides[key];
                        if (!value) {
                            return;
                        }
                    } else if (excludeValues[key]) {
                        return;
                    }
                    var item = items.get({id: key});
                    if (item) {
                        item.set({hidden: value === 0, value: value});
                    } else {
                        item = new Facet.Item({key: key, value: value});
                    }
                    newItems.push(item);
                }, this);

                function checkOther(set, key) {
                    if (set.indexOf(facet.other) !== -1) {
                        addNewItem(key, facetRanges[facetName][key]);
                    }
                }
                checkOther(['all', 'before'], 'before');

                _.each(list, function(value, index) {
                    if (index % 2 === 0) {
                        key = value;
                    } else if (value > 0) {
                        addNewItem(key, value);
                    }
                });
                switch (type) {
                    case 'year-field':
                    case 'field':
                        _.each(facet.bins, function(includeValues, key) {
                            var tag = facetName + ':' + key;
                            addNewItem(key, facetQueries[tag]);
                        });
                        break;
                }
                items.set(newItems, {remove: false});
                if (facet.facetStats) {
                    var thisFacetStats = statsFields[facetName];
                    facet.set({minValue: thisFacetStats.min, maxValue: thisFacetStats.max});
                }
            }, this));
        },
        getFacetFormData: function(options) {
            options = options || {};
            var result = {
                facet: true,
                stats: true,
                'facet.missing': true,
                'facet.field': [],
                'facet.range': [],
                'facet.interval': [],
                'facet.query': [],
                'stats.field': [],
                fq: [],
            };
            _.each(this.keys(), _.bind(function(facetName) {
                var facet = this.get(facetName);
                var queryField = facet.queryField;
                var facetField = facet.facetField ? facet.facetField : queryField;
                var type = facet.facetType;
                var valueFormatter;
                var facetOptions = {};
                var quoteFormatter = function(value) {
                    return value.replace ? ('"' + value.replace(/"/, '\\"') + '"') : value;
                };
                var rangeFormatter = function(from, to) {
                    return '[' + quoteFormatter(from) + ' TO ' + quoteFormatter(to) + ']';
                };
                switch (type) {
                    case 'year':
                        type = 'range';
                        quoteFormatter = function(value) {
                            return value.toISOString();
                        };
                        valueFormatter = function(item) {
                            var key = item.get('key');
                            var fromDate, toDate;
                            if (key === 'before') {
                                return '[* TO ' + facet.rangeStart + '-1SECOND]';
                            }
                            fromDate = new Date(key);
                            toDate = new Date(key);
                            toDate.setUTCFullYear(toDate.getUTCFullYear() + 1);
                            return rangeFormatter(fromDate, toDate);
                        };
                        if (facet.other) {
                            facetOptions['facet.range.other'] = facet.other;
                        }
                        break;
                    case 'range':
                    case 'interval':
                        valueFormatter = function(item) {
                            var key = parseInt(item.get('key'));
                            return rangeFormatter(key, key + facet.rangeGap);
                        };
                        break;
                    case 'year-field':
                        type = 'field';
                        quoteFormatter = function(value) {
                            return new Date(value).toISOString();
                        };
                        valueFormatter = facet.queryValue ? facet.queryValue : function(value) {
                            return quoteFormatter(getItemKeyAccessor(value));
                        };
                        _.each(facet.bins, function(includeValues, key) {
                            var query = _.map(includeValues, function(includeValue, index) {
                                return queryField + ':\'' + includeValue + '\'';
                            }).join(' OR ');
                            result['facet.query'].push('{!key=' + facetName + ':' + key + ' tag=' + facetName + ':' + key + '}(' + query + ')');
                        });
                        break;
                    case 'field':
                        valueFormatter = facet.queryValue ? facet.queryValue : function(value) {
                            return quoteFormatter(getItemKeyAccessor(value));
                        };
                        _.each(facet.bins, function(includeValues, key) {
                            var query = _.map(includeValues, function(includeValue, index) {
                                return queryField + ':' + includeValue;
                            }).join(' OR ');
                            result['facet.query'].push('{!key=' + facetName + ':' + key + ' tag=' + facetName + ':' + key + '}(' + query + ')');
                        });
                        break;
                }
                switch (type) {
                    case 'range':
                        facetOptions['facet.range.start'] = facet.rangeStart;
                        facetOptions['facet.range.end'] = facet.rangeEnd;
                        facetOptions['facet.range.gap'] = facet.rangeGap;
                        break;
                    case 'interval':
                        facetOptions['facet.interval.set'] = '[*,*]';
                        break;
                }
                var facetValues = [];
                var queryMax = facet.get('queryMax');
                var queryMin = facet.get('queryMin');
                if (queryMax !== undefined && queryMin !== undefined) {
                    facetValues.push(rangeFormatter(queryMin, queryMax));
                }
                facet.get('items').each(function(item, index) {
                    if (!item.get('hidden') && item.get('checked')) {
                        facetValues.push(valueFormatter(item));
                    }
                });
                facetOptions.key = facetName;
                if (facetValues.length) {
                    facetOptions.ex = facetName;
                    result.fq.push('{!tag=' + facetName + '}' + facetField + ':(' + facetValues.join(' OR ') + ')');
                } else {
                    facetOptions.tag = facetName;
                }
                if (!!options.facetSuggestions) {
                    var childFacet = options.childFacet;
                    if (childFacet === facet) {
                        facetOptions['facet.contains'] = facet.get('query');
                        facetOptions['facet.contains.ignoreCase'] = true;
                        facetOptions['facet.mincount'] = 1;
                    }
                }
                var facetOptionList = [];
                _.each(facetOptions, function(value, key) {
                    if (value !== undefined) {
                        facetOptionList.push(key + '=' + value);
                    }
                });
                result['facet.' + type].push('{!' + facetOptionList.join(' ') + '}' + queryField);
                if (facet.facetStats) {
                    result['stats.field'].push('{!' + facetOptionList.join(' ') + '}' + queryField);
                }
                var facetQuery = facet.get('query');
                if (facetQuery !== null && facetQuery !== '') {
                    facetOptions.key = 'suggestions:' + facetName;
                    facetOptionList = [];
                    _.each(facetOptions, function(value, key) {
                        if (value !== undefined) {
                            facetOptionList.push(key + '=' + value);
                        }
                    });
                    result['facet.' + type].push('{!' + facetOptionList.join(' ') + '}' + queryField);
                }
            }, this));
            return result;
        },



        toJSON: function toJSON(options) {
            if (!!options.facetSuggestions) {
                return _.extend({
                    rows: 0,
                    facet: true,
                    wt: 'json',
                    q: '*:*',
                }, this.getFacetFormData(options));
            }
        },
        parse: function parse(data, options) {
            if (!!options.facetSuggestions) {
                this.applyFacetResults(data, options);
            }
            return null;
        },
    }));
    Facets.Sort = Sort;
    Facets.Facet = Facet;

    return Facets;
});