PageSourceSearch

https://www.uc.edu/etc.clientlibs/news/components/content/search/c…e.lc-f3fe8f3bea8eb38d770865233317590a-lc.js

js uc.edu collected 2026-10-01 07:18:43 UTC 12,318 bytes, 278 lines download raw bytes

1angular.module("searchApp",['btorfs.multiselect']);
2(function ( $ ) {
3
4    function qs(key) {
5        key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, "\\$&"); // escape RegEx meta chars
6        var match = location.search.match(new RegExp("[?&]"+key+"=([^&]+)(&|$)"));
7        return match && decodeURIComponent(match[1].replace(/\+/g, " "));
8    }
9
10    // Get all values for a query param (for multi-value params like tags, authors)
11    function qsAll(key) {
12        var results = [];
13        var urlParams = new URLSearchParams(location.search);
14        urlParams.getAll(key).forEach(function(val) {
15            if (val) results.push(val);
16        });
17        return results;
18    }
19
20    // Extract userid from email format (e.g., "[email protected]" -> "hensonba")
21    // If not in email format, returns the input unchanged
22    function extractUserId(username) {
23        if (!username) return username;
24        var atIndex = username.indexOf('@');
25        if (atIndex > 0) {
26            return username.substring(0, atIndex);
27        }
28        return username;
29    }
30
31    // Check if URL has any search-related query parameters
32    function hasSearchQueryParams() {
33        var searchParams = ["term", "tags", "authors", "displayDateStart", "displayDateEnd", "showEvents", "force-search", "submitted"];
34        for (var i = 0; i < searchParams.length; i++) {
35            if (qs(searchParams[i]) != null) {
36                return true;
37            }
38        }
39        return false;
40    }
41
42    angular.module("searchApp")
43    .controller("searchController",  ["$timeout","$scope", function($timeout, $scope) {
44        //disabled angular for many search components
45        if($(".component.newssearch").size() > 1) {
46
47        } else {
48
49            var multiselectOptionsFieldNames =  ["authors"];
50            var namespaces = $(".component.newssearch").attr("data-namespaces").split(",");
51
52            multiselectOptionsFieldNames = multiselectOptionsFieldNames.concat(namespaces);
53
54            $(".taglist").hide();
55
56            // Track if user has interacted with search
57            var firstLoad = true;
58
59            $scope.init = function () {
60                //facets hold the data that is sent to the search
61                $scope.facets = {
62                    term: qs("term"),
63                    displayDateStart: (qs("displayDateStart") != null ? (new Date(parseInt(qs("displayDateStart"),10))).toDateString() : null),
64                    displayDateEnd: (qs("displayDateEnd") != null ? (new Date(parseInt(qs("displayDateEnd"),10))).toDateString() : null),
65                    showEvents: qs("showEvents"),
66                    authors: []
67                };
68                //add tags top each array
69                namespaces.forEach(function(ns) {
70                    $scope.facets[ns] = [];
71                });
72
73                //msoptions holds the options for the multiselect dropdowns
74                $scope.msOptions = {};
75                // Get URL params for tags and authors
76                // Support both generic "tags" param and namespace-specific params (e.g., "units", "colleges")
77                var urlTags = qsAll("tags");
78                var urlAuthors = qsAll("authors");
79                var urlTagsByNamespace = {};
80                namespaces.forEach(function(ns) {
81                    urlTagsByNamespace[ns] = qsAll(ns);
82                });
83
84                multiselectOptionsFieldNames.forEach(function(type) {
85                    if(typeof $scope.msOptions[type] === "undefined") {
86                        $scope.msOptions[type] = [];
87                    }
88                    $("#"+type+"-options button").each(function(index,element){
89                        let dataValue = $(element).attr("data-value");
90                        let option = {
91                            id: (type === "authors") ? extractUserId(dataValue) : dataValue,
92                            display: $(element).attr("data-display"),
93                            category: type
94                        };
95                        // Check if option is in URL params OR has data-selected attribute
96                        // Support both ?tags=units:online and ?units=units:online formats
97                        var isUrlSelected = (type === "authors")
98                            ? urlAuthors.indexOf(option.id) > -1
99                            : (urlTags.indexOf(option.id) > -1 || (urlTagsByNamespace[type] || []).indexOf(option.id) > -1);
100                        if($(element).is('[data-selected]') || isUrlSelected){
101                            $scope.facets[type].push(option);
102                        }
103                        $scope.msOptions[type].push(option);
104                    });
105
106                    // Add URL tags/authors that don't have matching UI buttons
107                    // Combine generic tags param with namespace-specific param
108                    var urlValues = (type === "authors")
109                        ? urlAuthors
110                        : urlTags.concat(urlTagsByNamespace[type] || []);
111                    urlValues.forEach(function(urlVal) {
112                        // For tags, only add to matching namespace (e.g., "topics:foo" goes to "topics")
113                        if (type !== "authors") {
114                            var tagNamespace = urlVal.split(":")[0];
115                            if (tagNamespace !== type) {
116                                return; // Skip - wrong namespace for this type
117                            }
118                        }
119                        var alreadyAdded = $scope.facets[type].some(function(f) { return f.id === urlVal; });
120                        if (!alreadyAdded) {
121                            var phantomOption = {
122                                id: urlVal,
123                                display: urlVal.split("/").pop(), // Use last segment as display
124                                category: type
125                            };
126                            $scope.facets[type].push(phantomOption);
127                            // Also add to msOptions so the multiselect directive recognizes it
128                            // and doesn't strip it from the selected values
129                            $scope.msOptions[type].push(phantomOption);
130                        }
131                    });
132                });
133
134                $("form.component.newssearch.update-live").on("submit",function(event){
135                    event.preventDefault();
136                });
137            }
138
139            $scope.init();
140
141            // Reload with URL params to apply filters
142            // Use $timeout to defer until after searchlist.js has added reloadWithUrlSearchParams method
143            if (hasSearchQueryParams()) {
144                $timeout(function() {
145                    doReload();
146                    setTitle();
147                }, 100);
148            }
149
150            //watch our facets for change, but wait a half second after a change
151            //NOTE: only reload if user has interacted OR if there were URL query params
152            $scope.timeout = $timeout(function(){});
153            $scope.$watch('facets',function(newVal,oldVal){
154                // Skip if init not complete or user hasn't interacted
155                if (firstLoad) {
156                    firstLoad = false;
157                    return;
158                }
159                console.log('$watch fired!', newVal);
160                //debounce so that it doesn't send a new search every keypress
161                $timeout.cancel($scope.timeout); //cancel the last timeout
162                $scope.timeout = $timeout(function(){
163                    console.log('Calling doReload after timeout');
164                    doReload();
165                    setTitle();
166                }, 500);
167            },true)
168
169            $scope.hasFilters = function() {
170                var hasFilter = false;
171                if($scope.facets.authors.length > 0) {
172                    hasFilter = true;
173                }
174                namespaces.forEach(function(type) {
175                    if($scope.facets[type].length > 0) {
176                        hasFilter = true;
177                    }
178                });
179                return hasFilter;
180            }
181            
182            $("input[name='term']").on("keydown",function(event){
183                if(event.keyCode == 13) {
184                    doReload();
185                    return false;
186                }
187            })
188            
189            function setTitle() {
190                if($("body").attr("data-template") != "results")
191                    return;
192
193                let tagsCombined = [];
194                namespaces.forEach(function (ns) {
195                    tagsCombined = tagsCombined.concat($scope.facets[ns]);
196                });
197                
198                let newTitle = "Search Results "
199
200                let authors = $scope.facets.authors;
201                if(authors.length == 1 && tagsCombined.length == 0) {
202                    newTitle = "Stories by " + authors[0].display + " -";
203                }
204                if(authors.length == 0 && tagsCombined.length == 1) {
205                    newTitle = "Stories tagged " + tagsCombined[0].display + " -"
206                }
207                
208                document.title = document.title.replace(document.title.substr(0, document.title.indexOf("-")),newTitle)
209                
210            }
211            setTitle() 
212
213            function doReload() {
214                //get the id of the target
215                var targetID = $("#search-target").attr("data-search-target");
216                //map the facets down to something we can use
217
218                //manualArticles=
219                // &term=
220                // &authors=
221                // &contacts=
222                // &displayDateRangeType=
223                // &displayDateStart=
224                // &displayDateEnd=
225                // &tags=
226                // &anyAll=
227                // &showEvents=
228                // &articlesExclude=
229                // &authorsExclude=
230                // &tagsExclude=
231                // &orderBy=
232                // &orderBySort=
233                // &limit=10
234                // &offset=30
235                let sendToSearch = new URLSearchParams();
236                sendToSearch.set("submitted","submitted"); // Required for server to use URL params as filters
237                sendToSearch.set("force-search","true");
238                sendToSearch.set("showEvents",$scope.facets.showEvents != null ? $scope.facets.showEvents : "");
239                sendToSearch.set("term",$scope.facets.term != null ? $scope.facets.term : "");
240                sendToSearch.set("displayDateStart",$scope.facets.displayDateStart != "" && $scope.facets.displayDateStart != null ? Date.parse($scope.facets.displayDateStart) : "");
241                sendToSearch.set("displayDateEnd",$scope.facets.displayDateEnd != "" && $scope.facets.displayDateEnd != null ? Date.parse($scope.facets.displayDateEnd) : "");
242
243                namespaces.forEach(function(ns) {
244                    $scope.facets[ns].forEach(function(tag){
245                        // Send as "tags" param - ArticleListFromRequest.java reads "tags", not namespace-specific params
246                        sendToSearch.append("tags", tag.id);
247                    });
248                });
249                $scope.facets.authors.forEach(function(author){
250                    sendToSearch.append("authors",author.id);
251                });
252                
253                if(typeof $("#" + targetID)[0] != "undefined" && typeof $("#" + targetID)[0].reloadWithUrlSearchParams != "undefined") {
254                    $("#" + targetID)[0].reloadWithUrlSearchParams(sendToSearch);
255                }
256            }
257
258            $scope.removeFacet = function(facet,category) {
259                $scope.facets[facet.category] = $scope.facets[facet.category].filter(function(el) {
260                    return (el.id != facet.id);
261                })
262                doReload();
263                return false;
264            }
265
266            $scope.removeAllFacets = function() {
267                $scope.facets.authors=[];
268
269                namespaces.forEach(function(ns){
270                    $scope.facets[ns] = [];
271                })
272
273                doReload();
274                return false;
275            }
276        }
277    }])
278}( jQuery ));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.