72920506 by Adam Heath

Split files.

1 parent bbb7f7fb
1 {
2 "directory": "src/lib/bower"
3 }
...@@ -11,7 +11,7 @@ module.exports = function(grunt) { ...@@ -11,7 +11,7 @@ module.exports = function(grunt) {
11 browserOptions: { 11 browserOptions: {
12 }, 12 },
13 model: [ 13 model: [
14 'src/scripts/solr-search/model/**/*.js', 14 'src/scripts/solr/model/**/*.js',
15 ], 15 ],
16 }; 16 };
17 /* 17 /*
......
...@@ -3,49 +3,15 @@ define(function(require) { ...@@ -3,49 +3,15 @@ define(function(require) {
3 var _ = require('underscore'); 3 var _ = require('underscore');
4 var Backbone = require('backbone'); 4 var Backbone = require('backbone');
5 var NestedModels = require('backbone-nested-models'); 5 var NestedModels = require('backbone-nested-models');
6 //var module = require('module');
7 6
8 function getField(obj, key) { 7 function getField(obj, key) {
9 return obj[key]; 8 return obj[key];
10 } 9 }
11 10
12 function mergeStaticProps(startPtr, endPtr, obj, fieldName) {
13 var result = obj;
14 var ptr = startPtr;
15 while (true) {
16 result = _.extend(result, _.result(ptr, fieldName));
17 if (ptr === endPtr) {
18 break;
19 }
20 ptr = ptr.__super__.constructor;
21 }
22 return result;
23 }
24
25 function mergeStaticSets(startPtr, endPtr, obj, fieldName) {
26 function arrayToMap(array) {
27 var result = {};
28 _.each(array, function(value) {
29 result[value] = true;
30 });
31 return result;
32 }
33 var set = arrayToMap(obj);
34 var ptr = startPtr;
35 while (true) {
36 set = _.extend(set, arrayToMap(_.result(ptr, fieldName)));
37 if (ptr === endPtr) {
38 break;
39 }
40 ptr = ptr.__super__.constructor;
41 }
42 return _.keys(set);
43 }
44
45 function getItemKeyAccessor(item) { 11 function getItemKeyAccessor(item) {
46 return item.get('key'); 12 return item.get('key');
47 } 13 }
48 var SolrFacets = (function() { 14
49 var Facets = NestedModels.mixin(Backbone.Model.extend({ 15 var Facets = NestedModels.mixin(Backbone.Model.extend({
50 initialize: function(data, options) { 16 initialize: function(data, options) {
51 this.url = function() { 17 this.url = function() {
...@@ -621,515 +587,4 @@ define(function(require) { ...@@ -621,515 +587,4 @@ define(function(require) {
621 }); 587 });
622 588
623 return Facets; 589 return Facets;
624 })();
625 var Pagination = (function() {
626 function stepFalse(e) {
627 e.preventDefault();
628 return false;
629 }
630 var Pagination = Backbone.Model.extend({
631 defaults: {
632 currentPage: 1,
633 hasNext: false,
634 hasNextJumpPage: false,
635 hasPrevious: false,
636 hasPreviousJumpPage: false,
637 nextJumpPage: undefined,
638 nextPage: undefined,
639 pages: [],
640 pageJump: 5,
641 pageSize: 10,
642 previousJumpPage: undefined,
643 previousPage: undefined,
644 totalCount: 0,
645 totalPages: 0,
646 },
647 initialize: function(data, options) {
648 var buildPages = function buildPages() {
649 var currentPage = parseInt(this.get('currentPage'));
650 var pageSize = parseInt(this.get('pageSize'));
651 var totalCount = parseInt(this.get('totalCount'));
652 if (!currentPage || !pageSize || !totalCount) {
653 return;
654 }
655 var pages = [];
656 var totalPages = Math.floor((totalCount + pageSize - 1) / pageSize);
657 function addPage(self, i) {
658 pages.push({
659 current: i === currentPage,
660 jump: function() {
661 self.set('currentPage', i);
662 return true;
663 },
664 number: i,
665 });
666 }
667 var startAt = currentPage - 4, endAt = currentPage + 5;
668 if (startAt < 1) {
669 endAt += (1 - startAt);
670 }
671 if (endAt > totalPages) {
672 startAt -= endAt - totalPages - 1;
673 endAt = totalPages + 1;
674 }
675 if (startAt < 1) {
676 startAt = 1;
677 }
678 if (endAt - startAt < 9) {
679 /* global console:false */
680 console.log('foo');
681 }
682
683 for (var i = startAt; i < endAt; i++) {
684 if (i > 0 && i <= totalPages) {
685 addPage(this, i);
686 }
687 }
688 var hasPrevious = currentPage > 1;
689 var hasNext = currentPage < totalPages;
690 var pageJump = this.get('pageJump');
691 var nextJumpPage, previousJumpPage, hasNextJump, hasPreviousJump;
692 if (pageJump) {
693 nextJumpPage = currentPage + pageJump;
694 previousJumpPage = currentPage - pageJump;
695 hasNextJump = nextJumpPage < totalPages;
696 hasPreviousJump = previousJumpPage > 0;
697 } else {
698 hasNextJump = false;
699 hasPreviousJump = false;
700 }
701 this.set({
702 hasNext: hasNext,
703 hasNextJump: hasNextJump,
704 hasPrevious: hasPrevious,
705 hasPreviousJump: hasPreviousJump,
706 nextJumpPage: hasNextJump ? nextJumpPage : undefined,
707 nextPage: hasNext ? currentPage + 1 : undefined,
708 pages: pages,
709 previousJumpPage: hasNextJump ? previousJumpPage : undefined,
710 previousPage: hasPrevious ? currentPage - 1 : undefined,
711 totalPages: totalPages,
712 });
713 };
714 this.on('change:pageSize change:currentPage change:totalCount', buildPages, this);
715 var installActions = _.bind(function installActions(eventName, nextName, hasNextName, previousName, hasPreviousName, pageCount) {
716 this.on(eventName, function() {
717 var hasNext = this.get(hasNextName);
718 var hasPrevious = this.get(hasPreviousName);
719 var next, previous;
720 if (hasNext) {
721 next = _.bind(function(e) {
722 e.preventDefault();
723 this.set('currentPage', this.get('currentPage') + pageCount);
724 return false;
725 }, this);
726 } else {
727 next = stepFalse;
728 }
729 if (hasPrevious) {
730 previous = _.bind(function(e) {
731 e.preventDefault();
732 this.set('currentPage', this.get('currentPage') - pageCount);
733 return false;
734 }, this);
735 } else {
736 previous = stepFalse;
737 }
738 this.set(nextName, next);
739 this.set(previousName, previous);
740 }, this);
741 this[nextName] = _.bind(function() {
742 return this.get(nextName)();
743 }, this);
744 this[previousName] = _.bind(function() {
745 return this.get(previousName)();
746 }, this);
747 }, this);
748 this.on('change:pageJump', function() {
749 var pageJump = this.get('pageJump');
750 installActions('change:hasNextJump change:hasPreviousJump', 'nextJump', 'hasNextJump', 'previousJump', 'hasPreviousJump', pageJump);
751 }, this);
752 installActions('change:hasNext change:hasPrevious', 'next', 'hasNext', 'previous', 'hasPrevious', 1);
753 buildPages.apply(this);
754 this.trigger('change:pageJump');
755 return Pagination.__super__.initialize.apply(this, arguments);
756 }
757 });
758 return Pagination;
759 })();
760 var Ordering = Backbone.Model.extend({
761 defaults: {
762 value: null,
763 items: new Backbone.Collection(),
764 },
765 initialize: function(data, options) {
766 if (this.get('value') === null) {
767 var firstItem = this.get('items').at(0);
768 if (firstItem) {
769 this.set('value', firstItem.get('value'));
770 }
771 }
772 },
773 parse: function(data) {
774 var result = _.clone(data);
775 if (result.items && !(result.items instanceof Backbone.Collection)) {
776 result.items = new Backbone.Collection(result.items, {parse: true});
777 }
778 return result;
779 },
780 });
781 var QueryTextField = Backbone.Model.extend({
782 defaults: {
783 formName: null,
784 name: null,
785 queries: [],
786 fields: null,
787 multi: false
788 },
789 });
790 var SolrSearch = Pagination.extend({
791 url: function url() {
792 return this.constructor.selectUrl;
793 },
794 defaults: function defaults() {
795 var constructor = this.constructor;
796 var formNameMap = {};
797 var facets = new SolrFacets(this.constructor.facets, {search: this});
798 _.each(facets.values(), function(facet) {
799 var formName = facet.get('formName');
800 if (formName) {
801 formNameMap[formName] = facet;
802 }
803 });
804 var queryFields = new Backbone.Model();
805 _.each(constructor.queryTextFields, function(definition, queryName) {
806 var qtf = new QueryTextField({formName: definition.formName, name: queryName, queries: [], fields: definition.fields, multi: !!definition.multi});
807 var formName = qtf.get('formName');
808 if (formName) {
809 formNameMap[formName] = qtf;
810 }
811 queryFields.set(queryName, qtf);
812 }, this);
813 return _.extend(_.result(SolrSearch.__super__, 'defaults'), {
814 initializing: true,
815 initialized: false,
816 query: '',
817 formNameMap: formNameMap,
818 results: new Backbone.Collection(),
819 ordering: new Ordering({items: constructor.orderingItems}, {parse: true}),
820 facets: facets,
821 queryFields: queryFields,
822 });
823 },
824 applyQueryParameters: function() {
825 var skipOptions = {skipSearch: true};
826 var parts = document.location.href.match(/.*\?(.*)/);
827 var facets = this.get('facets');
828 facets.resetSearch();
829 _.each(this.get('queryFields').values(), function(qtf) {
830 qtf.set({
831 query: null,
832 queries: [],
833 }, skipOptions);
834 });
835
836 if (parts) {
837 var formNameMap = this.get('formNameMap');
838 var keyValueParts = parts[1].split('&');
839 _.each(keyValueParts, function(keyValuePart, i) {
840 var keyFieldValue = keyValuePart.match(/^([^.]+)(?:\.([^.]+))?=(.*)$/);
841 if (keyFieldValue) {
842 var key = keyFieldValue[1];
843 var field = keyFieldValue[2];
844 var value = keyFieldValue[3];
845 value = value.replace(/(\+|%20)/g, ' ');
846 var impl = formNameMap[key];
847 if (impl) {
848 if (impl instanceof QueryTextField) {
849 impl.set('query', value, skipOptions);
850 } else if (impl.facetType === 'field' && value) {
851 if (field === 'min') {
852 impl.set('queryMin', parseInt(value), skipOptions);
853 } else if (field === 'max') {
854 impl.set('queryMax', parseInt(value), skipOptions);
855 } else if (field === 'items') {
856 var items = impl.get('items');
857 var item = items.get(value);
858 if (!item) {
859 item = new SolrFacets.Facet.Item({key: value, value: 0});
860 items.add(item);
861 item.on('change:checked', function(model, value, options) {
862 this.trigger('item-change', null, null, options);
863 impl.trigger('item-change', null, null, options);
864 }, facets);
865 }
866 item.set('checked', true, skipOptions);
867 }
868 }
869 }
870 }
871 }, this);
872 }
873 },
874 initialize: function(data, options) {
875 options = (options || {});
876 this.set('options', new Backbone.Model({
877 faceting: !!options.faceting,
878 highlighting: !!options.highlighting,
879 showAll: !!options.showAll,
880 }));
881 this._doSearchImmediately = _.bind(function(options) {
882 this.fetch(_.extend({}, options, {
883 data: this.toJSON(),
884 merge: false,
885 traditional: true,
886 }));
887 }, this);
888 this._doSearch = _.debounce(this._doSearchImmediately, 250);
889 this._doSearch = (function(doSearch) {
890 return function(options) {
891 var skipSearch = this._skipSearch || (options || {}).skipSearch;
892 if (!skipSearch) {
893 doSearch(options);
894 }
895 };
896 })(this._doSearch);
897 _.each(this.get('queryFields').values(), function(subQueryModel) {
898 subQueryModel.on('change:query change:queries', function(model, value, options) {
899 this.trigger('change:queryFields', null, null, options);
900 }, this);
901 }, this);
902 this.on('change:queryFields', function(model, value, options) {
903 this.trigger('change:query', null, null, options);
904 }, this);
905 this.get('options').on('change:faceting change:highlighting', function(model, value, options) {
906 this._doSearch(options);
907 }, this);
908 this.on('change:query', function(model, value, options) {
909 // this is silent, which causes the change events to not fire;
910 // this is ok, because the next .on will also see the change
911 // event on query, and resend the search
912 this._doSearch(_.extend({}, options, {resetCurrentPage: true, resetFacets: true, resetOrdering: true}));
913 }, this);
914 this.on('change:pageSize', function(model, value, options) {
915 this._doSearch(_.extend({}, options, {resetCurrentPage: true}));
916 }, this);
917 this.on('change:currentPage', function(model, value, options) {
918 this._doSearch(options);
919 }, this);
920 this.get('facets').on('item-change', function(model, value, options) {
921 this._doSearch(_.extend({}, options, {resetCurrentPage: true}));
922 }, this);
923 this.get('ordering').on('change:value', function(model, value, options) {
924 this._doSearch(_.extend({}, options, {resetCurrentPage: true}));
925 }, this);
926 return SolrSearch.__super__.initialize.apply(this, arguments);
927
928 },
929 parse: function parse(data, options) {
930 if (options.facetSuggestions) {
931 return null;
932 }
933 var skipOptions = _.extend({}, options, {skipSearch: true});
934 if (options.resetCurrentPage) {
935 this.set('currentPage', 1, skipOptions);
936 }
937 if (options.resetFacets) {
938 this.get('facets').resetSearch(skipOptions);
939 }
940 if (options.resetOrdering) {
941 this.get('ordering').set('value', this.get('ordering').get('items').at(0).get('value'), skipOptions);
942 }
943 var facets = this.get('facets');
944 if (this.get('options').get('faceting')) {
945 facets.applyFacetResults(data);
946 } else {
947 facets.resetSearch(options);
948 }
949 var list = [];
950 var highlighting = this.get('options').get('highlighting') ? data.highlighting : {};
951
952 var self = this;
953 _.each(data.response.docs, function(doc, index) {
954 var itemHighlighting = highlighting[doc.id];
955 if (!itemHighlighting) {
956 itemHighlighting = {};
957 }
958 list.push(self.searchParseDoc(doc, index, itemHighlighting));
959 });
960
961 return {
962 initializing: false,
963 initialized: true,
964 results: new Backbone.Collection(list),
965 facets: facets,
966 options: this.get('options'),
967 totalCount: data.response.numFound,
968 queryTime: (data.responseHeader.QTime / 1000).toFixed(2),
969 hasResults: list.length > 0,
970 };
971 },
972 searchParseDoc: function searchParseDoc(doc, index, itemHighlighting) {
973 var fieldsToParse = mergeStaticProps(this.constructor, SolrSearch, {}, 'parsedFieldMap');
974 var result = {};
975 _.each(fieldsToParse, function(value, key) {
976 var parsed;
977 if (_.isFunction(value)) {
978 parsed = value.call(this, doc, index, itemHighlighting, key);
979 } else if (_.isArray(value)) {
980 parsed = [];
981 _.each(value, function(item, i) {
982 var rawValue = doc[item.name];
983 if (rawValue) {
984 var parsedValue = item.parse.call(this, doc, index, itemHighlighting, rawValue, item.name);
985 if (parsedValue) {
986 parsed.push(parsedValue);
987 }
988 }
989 }, this);
990 } else {
991 parsed = doc[value];
992 }
993 result[key] = parsed;
994 }, this);
995 return result;
996 },
997 toJSON: function(options) {
998 if (!options) {
999 options = {};
1000 }
1001 var facets = this.get('facets');
1002 var constructor = this.constructor;
1003 var result = {
1004 defType: 'edismax',
1005 qf: constructor.queryField,
1006 wt: 'json',
1007 };
1008 var ordering = this.get('ordering');
1009 var sort = ordering.get('value');
1010 if (options.resetOrdering) {
1011 sort = ordering.get('items').at(0).get('value');
1012 }
1013 var currentPage = this.get('currentPage');
1014 var pageSize = this.get('pageSize');
1015 if (options.resetCurrentPage) {
1016 currentPage = 1;
1017 }
1018 result.sort = sort;
1019 result.rows = pageSize;
1020 result.start = (currentPage - 1) * pageSize;
1021 result.fl = mergeStaticSets(constructor, SolrSearch, [], 'returnFields');
1022 if (this.get('options').get('highlighting')) {
1023 result.hl = true;
1024 result['hl.fl'] = ['content', 'title'].concat(constructor.extraHighlightFields);
1025 }
1026
1027 if (this.get('options').get('faceting')) {
1028 var facetFormData = facets.getFacetFormData();
1029 var fq = facetFormData.fq;
1030 delete(facetFormData.fq);
1031 result = _.extend(result, facetFormData);
1032 if (fq.length) {
1033 if (result.fq) {
1034 result.fq = result.fq.concat(fq);
1035 } else {
1036 result.fq = fq;
1037 }
1038 }
1039 }
1040
1041 var queryParts = [];
1042 var queryFields = this.get('queryFields');
1043 _.each(queryFields.keys(), function(queryName) {
1044 var subQueryModel = queryFields.get(queryName);
1045 var fields = subQueryModel.get('fields');
1046 var queries = subQueryModel.get('queries');
1047 var query = subQueryModel.get('query');
1048 if (query) {
1049 queries = queries.concat([query]);
1050 }
1051 var subQuery = [];
1052 for (var i = 0; i < fields.length; i++) {
1053 var fieldName = fields[i];
1054 for (var j = 0; j < queries.length; j++) {
1055 // FIXME: quote the query
1056 subQuery.push(fieldName + ':\'' + queries[j] + '\'');
1057 }
1058 }
1059 if (subQuery.length) {
1060 queryParts.push(subQuery.join(' OR '));
1061 }
1062 }, this);
1063
1064 var query = queryParts.join(' AND ');
1065 if (!query) {
1066 query = '*:*';
1067 if (!this.get('options').get('showAll') && !result.fq) {
1068 result.rows = 0;
1069 }
1070 }
1071 var dateBoostField = constructor.dateBoostField;
1072 var popularityBoostField = constructor.popularityBoostField;
1073 if (result.rows !== 0 && (dateBoostField || popularityBoostField)) {
1074 var boostSum = [];
1075 if (dateBoostField) {
1076 result.dateBoost = 'recip(ms(NOW,' + dateBoostField + '),3.16e-11,1,1)';
1077 boostSum.push('$dateBoost');
1078 }
1079 if (popularityBoostField) {
1080 result.popularityBoost = 'def(' + popularityBoostField + ',0)';
1081 boostSum.push('$popularityBoost');
1082 }
1083 result.qq = query;
1084 result.q = '{!boost b=sum(' + boostSum.join(',') + ') v=$qq defType=$defType}';
1085 } else {
1086 result.q = query;
1087 }
1088 return result;
1089 },
1090 }, {
1091 cleanup: function cleanup(txt) {
1092 if (txt) {
1093 txt = txt.replace(/&amp;/g, '&');
1094 txt = txt.replace(/&#39;/g, '\'');
1095 txt = txt.replace(/[^a-zA-Z0-9 -\.,:;%<>\/'"|]/g, ' ');
1096 }
1097 return txt;
1098 },
1099 returnFields: [
1100 'keywords',
1101 'last_modified',
1102 'path',
1103 'score',
1104 'title',
1105 ],
1106 parsedFieldMap: {
1107 content: function(doc, index, itemHighlighting) {
1108 var content = itemHighlighting.content;
1109 if (content && content.constructor === Array) {
1110 content = content.join(' ');
1111 }
1112 if (!content) {
1113 content = '';
1114 }
1115 return this.constructor.cleanup(content);
1116 },
1117 keywords: 'keywords',
1118 lastModified: 'last_modified',
1119 path: 'url',
1120 score: 'score',
1121 title: function(doc, index, itemHighlighting) {
1122 var title;
1123 if (itemHighlighting.title) {
1124 title = itemHighlighting.title.join(' ');
1125 } else {
1126 title = doc.title[0];
1127 }
1128 return this.constructor.cleanup(title);
1129 },
1130 },
1131 });
1132 SolrSearch.Facets = SolrFacets;
1133 SolrSearch.Pagination = Pagination;
1134 return SolrSearch;
1135 }); 590 });
......
1 define(function(require) {
2 'use strict';
3 var Backbone = require('backbone');
4
5 var Ordering = Backbone.Model.extend({
6 defaults: {
7 value: null,
8 items: new Backbone.Collection(),
9 },
10 initialize: function(data, options) {
11 if (this.get('value') === null) {
12 var firstItem = this.get('items').at(0);
13 if (firstItem) {
14 this.set('value', firstItem.get('value'));
15 }
16 }
17 },
18 parse: function(data) {
19 var result = _.clone(data);
20 if (result.items && !(result.items instanceof Backbone.Collection)) {
21 result.items = new Backbone.Collection(result.items, {parse: true});
22 }
23 return result;
24 },
25 });
26 return Ordering;
27 });
1 define(function(require) {
2 'use strict';
3 var Backbone = require('backbone');
4
5 function stepFalse(e) {
6 e.preventDefault();
7 return false;
8 }
9 var Pagination = Backbone.Model.extend({
10 defaults: {
11 currentPage: 1,
12 hasNext: false,
13 hasNextJumpPage: false,
14 hasPrevious: false,
15 hasPreviousJumpPage: false,
16 nextJumpPage: undefined,
17 nextPage: undefined,
18 pages: [],
19 pageJump: 5,
20 pageSize: 10,
21 previousJumpPage: undefined,
22 previousPage: undefined,
23 totalCount: 0,
24 totalPages: 0,
25 },
26 initialize: function(data, options) {
27 var buildPages = function buildPages() {
28 var currentPage = parseInt(this.get('currentPage'));
29 var pageSize = parseInt(this.get('pageSize'));
30 var totalCount = parseInt(this.get('totalCount'));
31 if (!currentPage || !pageSize || !totalCount) {
32 return;
33 }
34 var pages = [];
35 var totalPages = Math.floor((totalCount + pageSize - 1) / pageSize);
36 function addPage(self, i) {
37 pages.push({
38 current: i === currentPage,
39 jump: function() {
40 self.set('currentPage', i);
41 return true;
42 },
43 number: i,
44 });
45 }
46 var startAt = currentPage - 4, endAt = currentPage + 5;
47 if (startAt < 1) {
48 endAt += (1 - startAt);
49 }
50 if (endAt > totalPages) {
51 startAt -= endAt - totalPages - 1;
52 endAt = totalPages + 1;
53 }
54 if (startAt < 1) {
55 startAt = 1;
56 }
57 if (endAt - startAt < 9) {
58 /* global console:false */
59 console.log('foo');
60 }
61
62 for (var i = startAt; i < endAt; i++) {
63 if (i > 0 && i <= totalPages) {
64 addPage(this, i);
65 }
66 }
67 var hasPrevious = currentPage > 1;
68 var hasNext = currentPage < totalPages;
69 var pageJump = this.get('pageJump');
70 var nextJumpPage, previousJumpPage, hasNextJump, hasPreviousJump;
71 if (pageJump) {
72 nextJumpPage = currentPage + pageJump;
73 previousJumpPage = currentPage - pageJump;
74 hasNextJump = nextJumpPage < totalPages;
75 hasPreviousJump = previousJumpPage > 0;
76 } else {
77 hasNextJump = false;
78 hasPreviousJump = false;
79 }
80 this.set({
81 hasNext: hasNext,
82 hasNextJump: hasNextJump,
83 hasPrevious: hasPrevious,
84 hasPreviousJump: hasPreviousJump,
85 nextJumpPage: hasNextJump ? nextJumpPage : undefined,
86 nextPage: hasNext ? currentPage + 1 : undefined,
87 pages: pages,
88 previousJumpPage: hasNextJump ? previousJumpPage : undefined,
89 previousPage: hasPrevious ? currentPage - 1 : undefined,
90 totalPages: totalPages,
91 });
92 };
93 this.on('change:pageSize change:currentPage change:totalCount', buildPages, this);
94 var installActions = _.bind(function installActions(eventName, nextName, hasNextName, previousName, hasPreviousName, pageCount) {
95 this.on(eventName, function() {
96 var hasNext = this.get(hasNextName);
97 var hasPrevious = this.get(hasPreviousName);
98 var next, previous;
99 if (hasNext) {
100 next = _.bind(function(e) {
101 e.preventDefault();
102 this.set('currentPage', this.get('currentPage') + pageCount);
103 return false;
104 }, this);
105 } else {
106 next = stepFalse;
107 }
108 if (hasPrevious) {
109 previous = _.bind(function(e) {
110 e.preventDefault();
111 this.set('currentPage', this.get('currentPage') - pageCount);
112 return false;
113 }, this);
114 } else {
115 previous = stepFalse;
116 }
117 this.set(nextName, next);
118 this.set(previousName, previous);
119 }, this);
120 this[nextName] = _.bind(function() {
121 return this.get(nextName)();
122 }, this);
123 this[previousName] = _.bind(function() {
124 return this.get(previousName)();
125 }, this);
126 }, this);
127 this.on('change:pageJump', function() {
128 var pageJump = this.get('pageJump');
129 installActions('change:hasNextJump change:hasPreviousJump', 'nextJump', 'hasNextJump', 'previousJump', 'hasPreviousJump', pageJump);
130 }, this);
131 installActions('change:hasNext change:hasPrevious', 'next', 'hasNext', 'previous', 'hasPrevious', 1);
132 buildPages.apply(this);
133 this.trigger('change:pageJump');
134 return Pagination.__super__.initialize.apply(this, arguments);
135 }
136 });
137 return Pagination;
138 });
1 define(function(require) {
2 'use strict';
3 var Backbone = require('backbone');
4
5 var QueryTextField = Backbone.Model.extend({
6 defaults: {
7 formName: null,
8 name: null,
9 queries: [],
10 fields: null,
11 multi: false
12 },
13 });
14 return QueryTextField;
15 });
1 define(function(require) {
2 'use strict';
3 var _ = require('underscore');
4 var Backbone = require('backbone');
5
6 var Facets = require('solr/model/Facets');
7 var Ordering = require('solr/model/Ordering');
8 var Pagination = require('solr/model/Pagination');
9 var QueryTextField = require('solr/model/QueryTextField');
10
11 //var module = require('module');
12
13 function mergeStatic(startPtr, endPtr, obj, fieldName, filterFunc) {
14 var result = filterFunc(obj);
15 var ptr = startPtr;
16 while (true) {
17 result = _.extend(result, filterFunc(_.result(ptr, fieldName)));
18 if (ptr === endPtr) {
19 break;
20 }
21 ptr = ptr.__super__.constructor;
22 }
23 return result;
24 }
25
26 function mergeStaticProps(startPtr, endPtr, obj, fieldName) {
27 return mergeStaticFunc(startPtr, endPtr, obj, fieldName, _.identity);
28 }
29
30 function mergeStaticSets(startPtr, endPtr, obj, fieldName) {
31 return _.keys(mergeStaticFunc(startPtr, endPtr, obj, fieldName, function arrayToMap(array) {
32 var result = {};
33 _.each(array, function(value) {
34 result[value] = true;
35 });
36 return result;
37 }));
38 }
39
40 function getItemKeyAccessor(item) {
41 return item.get('key');
42 }
43 var Solr = Pagination.extend({
44 url: function url() {
45 return this.constructor.selectUrl;
46 },
47 defaults: function defaults() {
48 var constructor = this.constructor;
49 var formNameMap = {};
50 var facets = new Facets(this.constructor.facets, {search: this});
51 _.each(facets.values(), function(facet) {
52 var formName = facet.get('formName');
53 if (formName) {
54 formNameMap[formName] = facet;
55 }
56 });
57 var queryFields = new Backbone.Model();
58 _.each(constructor.queryTextFields, function(definition, queryName) {
59 var qtf = new QueryTextField({formName: definition.formName, name: queryName, queries: [], fields: definition.fields, multi: !!definition.multi});
60 var formName = qtf.get('formName');
61 if (formName) {
62 formNameMap[formName] = qtf;
63 }
64 queryFields.set(queryName, qtf);
65 }, this);
66 return _.extend(_.result(Solr.__super__, 'defaults'), {
67 initializing: true,
68 initialized: false,
69 query: '',
70 formNameMap: formNameMap,
71 results: new Backbone.Collection(),
72 ordering: new Ordering({items: constructor.orderingItems}, {parse: true}),
73 facets: facets,
74 queryFields: queryFields,
75 });
76 },
77 applyQueryParameters: function() {
78 var skipOptions = {skipSearch: true};
79 var parts = document.location.href.match(/.*\?(.*)/);
80 var facets = this.get('facets');
81 facets.resetSearch();
82 _.each(this.get('queryFields').values(), function(qtf) {
83 qtf.set({
84 query: null,
85 queries: [],
86 }, skipOptions);
87 });
88
89 if (parts) {
90 var formNameMap = this.get('formNameMap');
91 var keyValueParts = parts[1].split('&');
92 _.each(keyValueParts, function(keyValuePart, i) {
93 var keyFieldValue = keyValuePart.match(/^([^.]+)(?:\.([^.]+))?=(.*)$/);
94 if (keyFieldValue) {
95 var key = keyFieldValue[1];
96 var field = keyFieldValue[2];
97 var value = keyFieldValue[3];
98 value = value.replace(/(\+|%20)/g, ' ');
99 var impl = formNameMap[key];
100 if (impl) {
101 if (impl instanceof QueryTextField) {
102 impl.set('query', value, skipOptions);
103 } else if (impl.facetType === 'field' && value) {
104 if (field === 'min') {
105 impl.set('queryMin', parseInt(value), skipOptions);
106 } else if (field === 'max') {
107 impl.set('queryMax', parseInt(value), skipOptions);
108 } else if (field === 'items') {
109 var items = impl.get('items');
110 var item = items.get(value);
111 if (!item) {
112 item = new Facets.Facet.Item({key: value, value: 0});
113 items.add(item);
114 item.on('change:checked', function(model, value, options) {
115 this.trigger('item-change', null, null, options);
116 impl.trigger('item-change', null, null, options);
117 }, facets);
118 }
119 item.set('checked', true, skipOptions);
120 }
121 }
122 }
123 }
124 }, this);
125 }
126 },
127 initialize: function(data, options) {
128 options = (options || {});
129 this.set('options', new Backbone.Model({
130 faceting: !!options.faceting,
131 highlighting: !!options.highlighting,
132 showAll: !!options.showAll,
133 }));
134 this._doSearchImmediately = _.bind(function(options) {
135 this.fetch(_.extend({}, options, {
136 data: this.toJSON(),
137 merge: false,
138 traditional: true,
139 }));
140 }, this);
141 this._doSearch = _.debounce(this._doSearchImmediately, 250);
142 this._doSearch = (function(doSearch) {
143 return function(options) {
144 var skipSearch = this._skipSearch || (options || {}).skipSearch;
145 if (!skipSearch) {
146 doSearch(options);
147 }
148 };
149 })(this._doSearch);
150 _.each(this.get('queryFields').values(), function(subQueryModel) {
151 subQueryModel.on('change:query change:queries', function(model, value, options) {
152 this.trigger('change:queryFields', null, null, options);
153 }, this);
154 }, this);
155 this.on('change:queryFields', function(model, value, options) {
156 this.trigger('change:query', null, null, options);
157 }, this);
158 this.get('options').on('change:faceting change:highlighting', function(model, value, options) {
159 this._doSearch(options);
160 }, this);
161 this.on('change:query', function(model, value, options) {
162 // this is silent, which causes the change events to not fire;
163 // this is ok, because the next .on will also see the change
164 // event on query, and resend the search
165 this._doSearch(_.extend({}, options, {resetCurrentPage: true, resetFacets: true, resetOrdering: true}));
166 }, this);
167 this.on('change:pageSize', function(model, value, options) {
168 this._doSearch(_.extend({}, options, {resetCurrentPage: true}));
169 }, this);
170 this.on('change:currentPage', function(model, value, options) {
171 this._doSearch(options);
172 }, this);
173 this.get('facets').on('item-change', function(model, value, options) {
174 this._doSearch(_.extend({}, options, {resetCurrentPage: true}));
175 }, this);
176 this.get('ordering').on('change:value', function(model, value, options) {
177 this._doSearch(_.extend({}, options, {resetCurrentPage: true}));
178 }, this);
179 return Solr.__super__.initialize.apply(this, arguments);
180
181 },
182 parse: function parse(data, options) {
183 if (options.facetSuggestions) {
184 return null;
185 }
186 var skipOptions = _.extend({}, options, {skipSearch: true});
187 if (options.resetCurrentPage) {
188 this.set('currentPage', 1, skipOptions);
189 }
190 if (options.resetFacets) {
191 this.get('facets').resetSearch(skipOptions);
192 }
193 if (options.resetOrdering) {
194 this.get('ordering').set('value', this.get('ordering').get('items').at(0).get('value'), skipOptions);
195 }
196 var facets = this.get('facets');
197 if (this.get('options').get('faceting')) {
198 facets.applyFacetResults(data);
199 } else {
200 facets.resetSearch(options);
201 }
202 var list = [];
203 var highlighting = this.get('options').get('highlighting') ? data.highlighting : {};
204
205 var self = this;
206 _.each(data.response.docs, function(doc, index) {
207 var itemHighlighting = highlighting[doc.id];
208 if (!itemHighlighting) {
209 itemHighlighting = {};
210 }
211 list.push(self.searchParseDoc(doc, index, itemHighlighting));
212 });
213
214 return {
215 initializing: false,
216 initialized: true,
217 results: new Backbone.Collection(list),
218 facets: facets,
219 options: this.get('options'),
220 totalCount: data.response.numFound,
221 queryTime: (data.responseHeader.QTime / 1000).toFixed(2),
222 hasResults: list.length > 0,
223 };
224 },
225 searchParseDoc: function searchParseDoc(doc, index, itemHighlighting) {
226 var fieldsToParse = mergeStaticProps(this.constructor, Solr, {}, 'parsedFieldMap');
227 var result = {};
228 _.each(fieldsToParse, function(value, key) {
229 var parsed;
230 if (_.isFunction(value)) {
231 parsed = value.call(this, doc, index, itemHighlighting, key);
232 } else if (_.isArray(value)) {
233 parsed = [];
234 _.each(value, function(item, i) {
235 var rawValue = doc[item.name];
236 if (rawValue) {
237 var parsedValue = item.parse.call(this, doc, index, itemHighlighting, rawValue, item.name);
238 if (parsedValue) {
239 parsed.push(parsedValue);
240 }
241 }
242 }, this);
243 } else {
244 parsed = doc[value];
245 }
246 result[key] = parsed;
247 }, this);
248 return result;
249 },
250 toJSON: function(options) {
251 if (!options) {
252 options = {};
253 }
254 var facets = this.get('facets');
255 var constructor = this.constructor;
256 var result = {
257 defType: 'edismax',
258 qf: constructor.queryField,
259 wt: 'json',
260 };
261 var ordering = this.get('ordering');
262 var sort = ordering.get('value');
263 if (options.resetOrdering) {
264 sort = ordering.get('items').at(0).get('value');
265 }
266 var currentPage = this.get('currentPage');
267 var pageSize = this.get('pageSize');
268 if (options.resetCurrentPage) {
269 currentPage = 1;
270 }
271 result.sort = sort;
272 result.rows = pageSize;
273 result.start = (currentPage - 1) * pageSize;
274 result.fl = mergeStaticSets(constructor, Solr, [], 'returnFields');
275 if (this.get('options').get('highlighting')) {
276 result.hl = true;
277 result['hl.fl'] = ['content', 'title'].concat(constructor.extraHighlightFields);
278 }
279
280 if (this.get('options').get('faceting')) {
281 var facetFormData = facets.getFacetFormData();
282 var fq = facetFormData.fq;
283 delete(facetFormData.fq);
284 result = _.extend(result, facetFormData);
285 if (fq.length) {
286 if (result.fq) {
287 result.fq = result.fq.concat(fq);
288 } else {
289 result.fq = fq;
290 }
291 }
292 }
293
294 var queryParts = [];
295 var queryFields = this.get('queryFields');
296 _.each(queryFields.keys(), function(queryName) {
297 var subQueryModel = queryFields.get(queryName);
298 var fields = subQueryModel.get('fields');
299 var queries = subQueryModel.get('queries');
300 var query = subQueryModel.get('query');
301 if (query) {
302 queries = queries.concat([query]);
303 }
304 var subQuery = [];
305 for (var i = 0; i < fields.length; i++) {
306 var fieldName = fields[i];
307 for (var j = 0; j < queries.length; j++) {
308 // FIXME: quote the query
309 subQuery.push(fieldName + ':\'' + queries[j] + '\'');
310 }
311 }
312 if (subQuery.length) {
313 queryParts.push(subQuery.join(' OR '));
314 }
315 }, this);
316
317 var query = queryParts.join(' AND ');
318 if (!query) {
319 query = '*:*';
320 if (!this.get('options').get('showAll') && !result.fq) {
321 result.rows = 0;
322 }
323 }
324 var dateBoostField = constructor.dateBoostField;
325 var popularityBoostField = constructor.popularityBoostField;
326 if (result.rows !== 0 && (dateBoostField || popularityBoostField)) {
327 var boostSum = [];
328 if (dateBoostField) {
329 result.dateBoost = 'recip(ms(NOW,' + dateBoostField + '),3.16e-11,1,1)';
330 boostSum.push('$dateBoost');
331 }
332 if (popularityBoostField) {
333 result.popularityBoost = 'def(' + popularityBoostField + ',0)';
334 boostSum.push('$popularityBoost');
335 }
336 result.qq = query;
337 result.q = '{!boost b=sum(' + boostSum.join(',') + ') v=$qq defType=$defType}';
338 } else {
339 result.q = query;
340 }
341 return result;
342 },
343 }, {
344 cleanup: function cleanup(txt) {
345 if (txt) {
346 txt = txt.replace(/&amp;/g, '&');
347 txt = txt.replace(/&#39;/g, '\'');
348 txt = txt.replace(/[^a-zA-Z0-9 -\.,:;%<>\/'"|]/g, ' ');
349 }
350 return txt;
351 },
352 returnFields: [
353 'keywords',
354 'last_modified',
355 'path',
356 'score',
357 'title',
358 ],
359 parsedFieldMap: {
360 content: function(doc, index, itemHighlighting) {
361 var content = itemHighlighting.content;
362 if (content && content.constructor === Array) {
363 content = content.join(' ');
364 }
365 if (!content) {
366 content = '';
367 }
368 return this.constructor.cleanup(content);
369 },
370 keywords: 'keywords',
371 lastModified: 'last_modified',
372 path: 'url',
373 score: 'score',
374 title: function(doc, index, itemHighlighting) {
375 var title;
376 if (itemHighlighting.title) {
377 title = itemHighlighting.title.join(' ');
378 } else {
379 title = doc.title[0];
380 }
381 return this.constructor.cleanup(title);
382 },
383 },
384 });
385 Solr.Facets = Facets;
386 Solr.Pagination = Pagination;
387 return Solr;
388 });