PageSourceSearch

https://www.grantforward.com/public/js/search.js?v=284

js grantforward.com collected 2026-09-24 18:37:13 UTC 96,191 bytes, 2,484 lines download raw bytes

1/**
2 * Some utility functions for date time adding and reconversion
3 */
4
5
6function getRegionDateFormat() {
7    return gettext('date_format_place_holder') !== "date_format_place_holder" ? gettext('date_format_place_holder') : "MM/DD/YYYY"
8}
9
10var Utils = Utils || {
11    //add one more day into a date object
12    addDays: function (date, amount) {
13        var tzOff = date.getTimezoneOffset() * 60 * 1000,
14            t = date.getTime(),
15            d = new Date(),
16            tzOff2;
17
18        t += (1000 * 60 * 60 * 24) * amount;
19        d.setTime(t);
20
21        tzOff2 = d.getTimezoneOffset() * 60 * 1000;
22        if (tzOff != tzOff2) {
23            var diff = tzOff2 - tzOff;
24            t += diff;
25            d.setTime(t);
26        }
27        return d;
28    },
29    getStringfromDate: function (date) {
30        //get component of date from it
31        var day = date.getDate();
32        var month = date.getMonth();
33        var year = date.getFullYear();
34
35        //since the function return month in 0-11 format
36        month = month + 1;
37
38        //add 0 to day or month in some cases below
39        day = (day < 10 ? '0' : '') + day;
40        month = (month < 10 ? '0' : '') + month;
41
42        return month + '/' + day + '/' + year;
43    },
44    //a compilation of those functions
45    addDayToString: function (date, amount) {
46        var dateObj = new Date(date);
47        var adjustedDateObj = Utils.addDays(dateObj, amount);
48        return Utils.getStringfromDate(adjustedDateObj);
49    }
50};
51
52function debounce(fn, delay) {
53    let timer = null;
54    return function () {
55        const context = this;
56        const args = arguments;
57
58        clearTimeout(timer);
59        timer = setTimeout(function () {
60            fn.apply(context, args);
61        }, delay);
62    };
63}
64
65/* Submission */
66Searcher.extend('MainSearchOpts', {
67    init: function (el, opts) {
68        this._super(el, opts);
69
70        this.refresh = false;
71        this.target = "/search/search";
72        this.content = $(".js-search-result");
73        this.hits = $(".js-result-text");
74        this.pagination = $(".js-pagination");
75        this.export_button = $("#export_button");
76        this.save_tooltip = $(".save_icon");
77        this.view_tooltip = $(".view_fav");
78        this.keyword_search = $("#keyword-in");
79        this.template_id = this.options.query.template_id;
80
81        this.query = debounce(this.query.bind(this), 500);
82    },
83    "query": function (save_offset, refresh_hits) {
84        if (save_offset === undefined) {
85            this.query_ob.remove_offset();
86        } else if (save_offset) {
87            //var y_scroll = $("#results_and_filters_holder").offset().top; //Top of the actual results
88            //window.scrollTo(0, y_scroll);
89        }
90        var kws = this.build_search_text();
91        // console.log("keyword search: " + kws);
92        if (kws.length > 0) {
93            this.query_ob.add_searchtext(kws);
94        } else {
95            this.query_ob.remove_searchtext();
96        }
97        this._super(refresh_hits);//refresh_hits can be undefined, we'll catch it in superclass.
98        $("#limit").val(this.query_ob.limit);
99    },
100    escapeHtml: function (value) {
101        return String(value || '');
102    },
103    build_search_text: function () {
104        var search_text = "";
105        var template_id = this.template_id;
106        var self = this;
107
108        if (typeof(window.smartSearchQuery)!=='undefined' && typeof(window.smartSearchQuery.value)!=='undefined') {
109        // Check if element with class query_form has class hidden or not using Jquery
110        // If it has class hidden, then return the value of the smart search query
111        // Else, return the value of the search input
112            if (!$('.query_form').hasClass('hidden')) {
113                return window.smartSearchQuery.value();
114            }
115        }
116        var val = $.trim($('.js-search-input').val());
117        if (template_id) {
118            return val;
119        }
120        $(".js-search-input").each(function () {
121            if ($(this).attr("default") == "on") {
122                return true; //Skip defaults
123            }
124
125            var ktype = $(this).attr("ktype");
126            var input = self.escapeHtml($(this).val()).trim();
127
128            if (input.length > 0) {
129
130                var extractReg = /[-]?\"[^\"]*\"|[+]?\"[^\"]*\"|[-]?\'[^\'']*\'|[+]?\'[^\'']*\'|\S+/gi;
131                var checkQuoteReg = /[-]?\"[^\"]*\"|[+]?\"[^\"]*\"|[-]?\'[^\'']*\'|[+]?\'[^\'']*\'+/gi;
132
133                //GS 2.6.2: support comma as separators as well
134                testCommaInput = [input];
135
136                var usedComma = testCommaInput.length > 1;
137
138                for (var i = 0; i < testCommaInput.length; i++) {
139
140                    var values;
141
142                    inputItem = jQuery.trim(testCommaInput[i]);
143                    //if comma is used, and a part of it is not using quotes, treat it as one phrase
144                    var testMatches = inputItem.match(checkQuoteReg);
145                    //also check if this is a phrase or a word
146                    var checkSpaces = inputItem.replace(/  +/g, ' ').split(' ');
147                    if (!testMatches && checkSpaces.length > 1 && usedComma) {
148                        //add quote to the inputItem
149                        inputItem = '"' + inputItem + '"';
150                        values = [inputItem];
151                    } else {
152                        values = inputItem.match(extractReg);
153                    }
154                    if (values) {
155                        var full = true;
156                        $(values).each(function () {
157                            value = $.trim(this);
158                            if (value.length > 0) {
159                                var first = value[0];
160                                if (first == '+' || first == '-') value = value.substring(1);
161                                var quoted = value.length > 1 && value[0] == value[value.length - 1] && (value[0] == '"' || value[0] == '\'');
162                                if (quoted) value = value.substring(1, value.length - 1);
163                                if (value[0] == "(") quoted = true;
164
165                                if (value.length > 0) {
166                                    if (ktype == "exclude") {
167                                        search_text += ' ' + (value[0] != '-' ? '-' : '') + (quoted ? '"' + value + '"' : value);
168                                    } else if (ktype == "all") {
169                                        search_text += ' ' + (value[0] != '+' ? '+' : '') + (quoted ? '"' + value + '"' : value);
170                                    } else if (ktype == "in") {
171                                        search_text += ' ' + (quoted ? '"' + value + '"' : value);
172                                    } else if (ktype == "exact") {
173                                        full = full && !quoted;
174                                    }
175                                }
176                            }
177                        });
178                        if (ktype == "exact") {
179                            if (full) {
180                                search_text += ' +"' + inputItem + '"';
181                            } else {
182                                $(values).each(function () {
183                                    value = $.trim(this);
184                                    if (value.length > 0) {
185                                        var first = value[0];
186                                        if (first == '+' || first == '-') value = value.substring(1);
187                                        var quoted = value.length > 1 && value[0] == value[value.length - 1] && (value[0] == '"' || value[0] == '\'');
188                                        if (quoted) value = value.substring(1, value.length - 1);
189
190                                        if (value.length > 0) {
191                                            search_text += ' +"' + value + '"';
192                                        }
193                                    }
194                                });
195                            }
196                        }
197                    }
198                }
199            }
200        });
201        var keyword_text = $.trim(search_text);
202        //
203        // if (typeof(window.smartSearchQuery)!=='undefined' && typeof(window.smartSearchQuery.value)!=='undefined') {
204        //     // Check if element with class query_form has class hidden or not using Jquery
205        //     // If it has class hidden, then return the value of the smart search query
206        //     // Else, return the value of the search input
207        //         if ($('.query_form').hasClass('hidden')) {
208        //             window.smartSearchQuery.setVal(keyword_text);
209        //         }
210        // }
211        return keyword_text;
212    }
213});
214
215$.Controller("SortHandler", {
216    init: function (el, opts) {
217        var control = this;
218        this.element.sortable({
219            handle: ".advanced_item",
220            placeholder: "advanced_holder_highlight",
221            update: function () {
222                control.update_order();
223            }
224        });
225
226        this.original_order = ["subject", "sponsors", "earliest_submission", "type", "grant_types", "status", "is_limited", "applicant_types"];
227
228        this.element.hide();
229        this.set_order();
230        this.element.show();
231
232        this.set_hidden();
233    },
234    ".advanced_minimize_holder click": function (el, ev) {
235        this.minimize(el);
236        this.update_hidden();
237    },
238    minimize: function (el) {
239        var maximize = $(".advanced_maximize", el);
240        if (maximize.length == 0) {
241            el.append("<div class='advanced_maximize'></div>");
242            el.parents(".advanced_holder").find(".options").slide
242Up();
243        } else {
244            maximize.remove();
245            el.parents(".advanced_holder").find(".options").slideDown();
246        }
247    },
248    update_order: function () {
249        var order = this.element.sortable("toArray", { attribute: "for" });
250
251        var same = true;
252        for (i = 0; i < order.length; i++) {
253            if (order[i] != this.original_order[i]) {
254                same = false;
255                break;
256            }
257        }
258
259        if (same) {
260            $.cookie('filter_order', null, { path: '/' });
261        } else {
262            $.cookie('filter_order', { "order": order }, { expires: 3600, path: '/' });
263        }
264    },
265    set_order: function () {
266        var order = $.cookie("filter_order");
267        if (order == null) {
268            return true;
269        }
270
271        var objects = {};
272
273        this.element.find("li").each(function () {
274            var for_li = $(this).attr("for");
275
276            if (jQuery.inArray(for_li, order['order']) < 0) {
277                order['order'].push(for_li);
278            }
279
280            objects[for_li] = $(this).detach();
281        });
282
283        var control = this;
284        $.each(order['order'], function (i, v) {
285            control.element.append(objects[v]);
286        });
287    },
288    update_hidden: function () {
289        var control = this;
290        var hidden_list = {};
291        this.element.find(".advanced_maximize").each(function () {
292            var for_li = $(this).parents(".advanced_holder").attr("for");
293            hidden_list[for_li] = true;
294        });
295
296        if ($.isEmptyObject(hidden_list)) {
297            $.cookie('filter_hidden', null, { path: '/' });
298        } else {
299            $.cookie('filter_hidden', hidden_list, { expires: 3600, path: '/' });
300        }
301    },
302    set_hidden: function () {
303        /*If we need to, this can be inlined with set_order,
304        since we iterate over the pieces anyway, except we don't
305        if they're in natural order already*/
306        var hidden = $.cookie("filter_hidden");
307        if (hidden == null) {
308            return true;
309        }
310        control = this;
311        $.each(hidden, function (i, v) {
312            /*Uh.... */
313            var mini = control.element.find("li[for='" + i + "']").find(".advanced_minimize_holder");
314            control.minimize(mini);
315        });
316    }
317});
318
319
320/*Search Tools*/
321MainSearchOpts.extend("SearchTools", {
322    init: function (el, opts) {
323        this._super(el, opts);
324
325        this.out_pickerr = this.options.export_picker; //By Deep
326
327        if (this.query_ob.edit === true) {
328            $(".js-action-save").addClass('disabled');
329        }
330
331        $('.save-search-popup').save_alert_form({ "query": this.query_ob });
332        $('.load-search-popup').load_alert_form({ "query": this.query_ob });
333
334        this.exportOptionsPopover = new PopoverUtils({
335            trigger: '#export-grant-selection-trigger',
336            content: '#export-grant-selection',
337            ignoreCloseEl: ".js-action-export",
338            id: "Export Popover"
339        })
340
341        this.selectedGrantIds = [];
342        $(document).on("czd_grant_selection_change", function (_, data){
343            this.selectedGrantIds = Array.from(data.selectedGrants)
344        }.bind(this));
345
346        //preventing non-reloading nature of the remote modal call
347        // $(document).on('hidden.bs.modal', '.save-search-popup', function () {
348        //     $(this).find('.modal-content').html('');
349        //     $(this).removeData('bs.modal');
350        // });
351    },
352    ".js-action-export click": function (el, ev) {
353        if (el.hasClass("disabled")) {
354            return false;
355        }
356        if (this.selectedGrantIds.length) {
357            this.exportOptionsPopover.togglePopover()
358            return;
359        }
360
361        this.out_pickerr.export_picker("show_picker", el);
362    },
363    // Export options handler
364    '.js-export-selected-grants click': function (el) {
365        this.out_pickerr.export_picker("show_picker", el, {
366            selectedGrantIds: this.selectedGrantIds
367        });
368    },
369    '.js-export-all-result click': function (el) {
370        this.out_pickerr.export_picker("show_picker", el);
371    },
372    "#sort change": function (el) {
373        this.query_ob.add_sortby(el.val());
374        this.query(undefined, false);//We don't want to save the page, but we do want to block hit refresh..
375        //2.4.1: trackers
376        _trackGSEvent(
377            '2.0-Search',
378            'sort_change',
379            el.val()
380        );
381    },
382    "#direction change": function (el) {
383        this.query_ob.sortby = $("#sort").val();
384        this.query_ob.add_sortDirection(el.val());
385        this.query(undefined, false);//We don't want to save the page, but we do want to block hit refresh..
386        //2.4.1: trackers
387        _trackGSEvent(
388            '2.0-Search',
389            'sort_direction_change',
390            el.val()
391        );
392    },
393    "#limit change": function (el) {
394        this.query_ob.add_limit(el.val());
395        this.query(undefined, false);//We don't want to save the page, but we do want to block hit refresh..
396        //2.4.1: trackers
397        _trackGSEvent(
398            '2.0-Search',
399            'limit_change',
400            el.val()
401        );
402    },
403    ".js-action-save click": function (el, ev) {
404        if (el.hasClass("disabled")) {
405            ev.preventDefault();
406            return false;
407        }
408        //disable caching on IE8 by using timestamp
409        var date = new Date();
410        $('.save-search-popup').data('js-refocus-target', '.btn-action-save').modal({
411            remote: "/user/render_save_alert_form?t=" + date.getTime()
412        });
413
414        //2.4.1: trackers
415        _trackGSEvent(
416            '2.0-Search',
417            'save_search_open',
418            ''
419        );
420    },
421    ".js-action-load click": function (el, ev) {
422        if (el.hasClass("disabled")) {
423            ev.preventDefault();
424            return false;
425        }
426        var date = new Date();
427        $('.load-search-popup').data('js-refocus-target', '.btn-action-save').modal({
428            remote: "/user/render_load_alert_form?t=" + date.getTime()
429        });
430
431        //2.4.1: trackers
432        _trackGSEvent(
433            '2.0-Search',
434            'load_search_open',
435            ''
436        );
437    },
438    ".js-action-share click": function (el, ev) {
439        this.getSearchUrl(function (url) {
440            if (el.hasClass("disabled")) {
441                ev.preventDefault();
442                return false;
443            }
444            var date = new Date();
445
446            $("#shared-link").val(url);
447            $('.share-search-popup').data('js-refocus-target', el).modal('show');
448
449            //2.4.1: trackers
450            _trackGSEvent(
451                '2.0-Search',
452                'shared_search_open',
453                ''
454            );
455        }, function () { /** error callback */
456            bootbox.alert('There were some problems with sharing search right now, please try again in a short while!');
457        });
458    },
459    // Get Search Url
460    // Params: callback function for when Done
461    // @params function cbError callback when the function got an error
462    getSearchUrl: function (cbDone, cbError) {
463        var control = this;
464        var search_params = this.query_ob.construct(false);
465        var editorMode ='basic';
466        if($('.query_form').length>0 && !$('.query_form').hasClass('hidden')) {
467            editorMode='advanced';
468        }
469        var search_text = this.build_search_text();
470        solr_search = null;
471        if (search_params) {
472            solr_search = search_params['qs'];
473        }
474        if (this.query_ob.sortby) {
475            var sort_field = this.query_ob.sortby;
476            var sort_direction = this.query_ob.sortDirection;
477            var input = JSON.stringify({ 'solr_search': solr_search, 'search_text': search_text, 'sort_field': sort_field, 'editor_mode': editorMode, 'sort_direction': sort_direction });
478        }
479        else {
480            var input = JSON.stringify({ 'solr_search': solr_search, 'search_text': search_text, 'editor_mode': editorMode });
481        }
482
483        $.ajax({
484            type: "POST",
485            url: "/search/get_shared_search_url",
486            data: {
487                input: input
488            },
489            dataType: "json",
490            success: function (response) {
491                if (response && response.status === 1) {
492                    if (cbDone)
493                        cbDone(response.data);
494                }
495                else {
496                    if (typeof (cbError) == 'function') {
497                        cbError(response.message);
498                    }
499                }
500            },
501            error: function (xhr, status, errorThrown) {
502                if (typeof (cbError) == 'function') {
503                    cbError(response.message);
504                }
505            }
506        });
507    },
508});
509
510/*Loader*/
511MainSearchOpts.extend("QueryLoader", {
512    init: function (el, opts) {
513        this._super(el, opts);
514
515        if (typeof (opts.applicant_locations_filter) != 'undefined') {
516            this.applicantLocationFilter = opts.applicant_locations_filter;
517        }
518
519        if (typeof (opts.activity_locations_filter) != 'undefined') {
520            this.activityLocationFilter = opts.activity_locations_filter;
521        }
522
523        if (typeof (opts.amount_filter) != 'undefined') {
524            this.amountFilter = opts.amount_filter;
525        }
526
527        if (typeof (opts.cfda_filter) != 'undefined') {
528            this.cfdaFilter = opts.cfda_filter;
529        }
530
531        if (typeof (opts.applicant_type_filter) != 'undefined') {
532            this.applicantTypeFilter = opts.applicant_type_filter;
533        }
534
535        if (typeof (opts.sponsor_filter) != 'undefined') {
536            this.sponsorFilter = opts.sponsor_filter;
537        }
538        if (typeof (opts.sponsor_country_filter) != 'undefined') {
539            this.sponsorCountryFilter = opts.sponsor_country_filter;
540        }
541
542        if (typeof (opts.citizenship_filter) != 'undefined') {
543            this.citizenshipFilter = opts.citizenship_filter;
544        }
545
546        this.searchFilterToggle = typeof (opts.searchFilterToggle) != 'undefined' ? opts.searchFilterToggle : null;
547
548        this.func_map = {
549            "subject": this.set_subject,
550            "sponsors": this.set_sponsors,
551            "earliest_submission": this.set_date,
552            "type": this.set_advanced_input,
553            "grant_types": this.set_advanced_input,
554            "status": this.set_advanced_input,
555            "is_limited": this.set_advanced_input,
556            // "applicant_locations": this.set_applicant_locations
557
558            /*"type":this.set_advanced,
559            "grant_types":this.set_advanced,
560            "status":this.set_basic,
561            "is_limited":this.set_basic,
562            "applicant_types":this.set_basic*/
563        };
564        this.render();
565    },
566    render: function () {
567        //console.log("QueryLoader render");
568        //Clear keywords - contained within this controller
569        //query_loader('render');
570        if (this.element.find(".js-search-input").length > 0)
571            this.element.find(".js-search-input").val("");
572
573        //Clear categories
574        $(".js-category-items").selected_categories("clear_selected", false);
575
576        //Clear dates
577        // $('#date-filter .js-clear-input > a').click();
578        $("#date-filter").date_filter("clear_input", false);
579        $("#date-filter").date_filter("clear_relative_deadline", false);
580
581        //The rest
582        $('.js-advanced-input').removeClass('input-active').removeClass('input-negative');
583        $('.advanced-input-control-negative').removeClass('active');
584        $('.advanced-input-control-positive').removeClass('active');
585
586        //sort by also needs resetting (GS 2.5 fix)
587        $('#sort').val('');
588        $('#direction').val('');
589
590        $('#limit').val('');
591
592        this.load_limit();
593
594        this.load_offset();
595
596        this.load_sortby();
597        this.load_sortDirection();
598
599        this.load_searchtext();
600        this.load_advanced();//This will also unhide any filters that are active
601        this.load_relative_deadline();
602
603        this.load_amount(this); //if there is anything to load
604
605        this.load_cfda(this);
606
607        this.load_applicant_type(this);
608
609        this.load_sponsor(this);
610
611        this.load_sponsor_country(this);
612
613        this.load_applicant_locations(this);
614
615        this.load_activity_locations(this);
616
617        this.load_citizenship(this);
618
619        this.query(true);
620
621        if (this.searchFilterToggle != null)
622            this.searchFilterToggle.checkOpenFilter();
623    },
624    load_limit: function () {
625        $("#limit").val(this.query_ob.limit);
626    },
627    load_offset: function () {
628        //Offset always points to the next page, so if we don't "rewind," then refreshing will move up pages
629        // pos = $.inArray(this.query_ob.current_offset, this.query_ob.offset) - 1;
630        // if (pos < 0) {
631        //     pos = 0;
632        // }
633        // var offset = this.query_ob.offset;
634        // this.query_ob.add_offset(offset[pos]);
635    },
636    load_sortby: function () {
637        $("#sort").val(this.query_ob.sortby);
638        this.query_ob.add_sortby(this.query_ob.sortby, false);
639    },
640    load_sortDirection: function () {
641        $("#direction").val(this.query_ob.sortDirection);
642        this.query_ob.add_sortDirection(this.query_ob.sortDirection);
643    },
644    load_searchtext: function () {
645        // If the search text is null or undefine, assign "" into that
646        if (this.query_ob.search_text == null || this.query_ob.search_text == undefined) {
647            this.query_ob.search_text = "";
648        }
649
650        // Check if this.query_ob.search_text is advance mode or not by check if query including title:(), description:(, AND, OR
651        var isAdvanceMode = this.query_ob.search_mode == "advanced" ? true : false;
652        window.isBasicMode = !isAdvanceMode;
653
654        if ((typeof(window.smartSearchQuery)!=='undefined' && typeof(window.smartSearchQuery.value)!=='undefined') || isAdvanceMode) {
655            window.smartSearchQuery.setVal(this.query_ob.search_text);
656            // Wait for a small delay before refreshing the view
657            setTimeout(() => {
658                window.smartSearchQuery.refreshView();
659            }, 100);
660            if (typeof(window.smartSearchResponse)!=='undefined' && typeof(window.smartSearchResponse.original_query)!=='undefined') {
661                // Fill in text area with id smart-search-prompt with the original query
662                $('#smart-search-prompt').val(window.smartSearchResponse.original_query);
663            }
664            // return ;
665        }
666        // If the search text is not in advance mode, then fill in the search input
667        if(isAdvanceMode == false){
668            var values = this.query_ob.search_text.match(/[[^\]]*\]|[-]?\"[^\"]*\"|[+]?\"[^\"]*\"|[[^\]]*\]|[-]?\'[^\'']*\'|[+]?\'[^\'']*\'|\S+/gi);
669            var normalize = function (str) {
670                if (typeof str == 'string') {
671                    if (str.length > 0 && (str[0] == '+' || str[0] == '-')) {
672                        str = str.substring(1);
673                    }
674                    if (str.length > 1 && (str[0] == '\'' && str[str.length - 1] == '\'')) {
675                        str = '"' + str.substring(1, str.length - 2) + '"';
676                    }
677                    // if (str.length > 1 && (str[0] == '"' && str[str.length-1] == '"')) {
678                    //     str = str.substring(1, str.length-2);
679                    // }
680                }
681                return str;
682            };
683            var keyword_box = {
684                "in": [],
685                "exact": [],
686                "all": [],
687                "exclude": []
688            };
689            if (values != null) {
690                $(values).each(function () {
691                    var value = $.trim(this);
692                    if (value.length > 0) {
693                        var first = value[0];
694                        if (first == '+' || first == '-') value = value.substring(1);
695                        var quoted = value.length > 1 && value[0] == value[value.length - 1] && (value[0] == '"' || value[0] == '\'');
696                        if (quoted) value = value.substring(1, value.length - 1);
697
698                        if (first == '-') {
699                            keyword_box['exclude'].push(quoted ? '"' + value + '"' : value);
700                        } else if (first == '+') {
701                            keyword_box['all'].push(quoted ? '"' + value + '"' : value);
702                        } else {
703                            keyword_box['in'].push(quoted ? '"' + value + '"' : value);
704                        }
705                    }
706                });
707            }
708
709            $.each(keyword_box, function (i, v) {
710                try {
711                    if (v.length > 0) {
712                        var value = v.join(" ");
713                        if (i == "exact" && v.length == 1 && (value.length > 1 && value[0] == value[value.length - 1] && (value[0] == '"' || value[0] == '\''))) value = value.substring(1, value.length - 1);
714                        var el = $("#keyword-" + i + "");
715                        el.attr("default", "off");
716                        el.val(value);
717                    }
718                } catch (e) { }
719            });
720
721            if (keyword_box["exact"].length > 0 ||
722                keyword_box["in"].length > 0 ||
723                keyword_box["exclude"].length > 0) {
724
725                $(".js-keyword-options").addClass('advanced');
726            }
727            $(".more-operator").removeClass('hidden');
728            $('.less-operator').addClass('hidden');
729            $('.keyword_form').removeClass('hidden');
730            $('.query_form').addClass('hidden');
731            $('.suggested-keyword-old-ctn').removeClass('hidden');
732        }
733        else{
734            $(".more-operator").addClass('hidden');
735            $('.less-operator').removeClass('hidden');
736            $('.keyword_form').addClass('hidden');
737            $('.query_form').removeClass('hidden');
738            $('.suggested-keyword-old-ctn').addClass('hidden');
739        }
740    },
741    load_advanced: function () {
742        var control = this;
743        var filter_hidden = $.cookie("filter_hidden");
744        if (filter_hidden == null) {
745            filter_hidden = {};
746        }
747        $(".js-filter-item").each(function () {
748            var attr_for = $(this).attr("for");
749
750            if (!(attr_for in control.query_ob.advanced)) {
751                return true;
752            }
753
754            delete filter_hidden[attr_for]; //Any filter in use should not be minimized
755
756            var func = control.func_map[attr_for];
757            if (typeof (func) == 'function') {
758                func($(this), control, attr_for);
759            }
760        });
761
762        //when we query with both continuous status, and deadline
763        //the continuous status will be ignored
764        //we wouldn't need to change anything on logic side
765        //but we should show something that is more explicit to users
766        //deactivate it if it is active, then disable the button
767
768        var continuousButton = $('.status-filter-Continuous');
769        if (control.query_ob.advanced && control.query_ob.advanced['status']) {
770            if (Object.keys(control.query_ob.advanced['status']).length == 1 && control.query_ob.advanced['status']['Continuous'] && control.query_ob.advanced['earliest_submission']){
771                //remove logically
772                control.query_ob.remove_advanced("status", 'Continuous');
773                //then aesthetically
774                continuousButton.removeClass('input-active');
775            }
776        }
777
778        // if (control.query_ob.advanced && control.query_ob.advanced['earliest_submission']) {
779        //     //remove logically
780        //     control.query_ob.remove_advanced("status", 'Continuous');
781        //     //then aesthetically
782        //     continuousButton.removeClass('input-active');
783
784        //     continuousButton
785        //         .addClass('disabled').attr('aria-disabled', 'true')
786        //         .attr('title', 'This option is unavailable when a deadline is selected.');
787        // }
788
789        // if ( typeof(control.query_ob.advanced['has_applicant_locations']) != 'undefined' && typeof(control.query_ob.advanced['has_applicant_locations']['1']) != 'undefined' ){
790        //      $('.include-unspecified input').prop('checked', true);
791        // }else{
792        //      $('.include-unspecified input').prop('checked', false);
793        // }
794
795        if ($.isEmptyObject(filter_hidden)) {//Update the filter hidden cookie
796            $.cookie('filter_hidden', null, { path: '/' });
797        } else {
798            $.cookie('filter_hidden', filter_hidden, { expires: 3600, path: '/' });
799        }
800    },
801    load_relative_deadline: function () {
802        var control = this;
803        if ("relative_deadline" in control.query_ob.advanced) {
804            $.each(control.query_ob.advanced["relative_deadline"], function (index, ob) {
805                //remove logically
806                // var continuousButton = $('.status-filter-Continuous');
807                // control.query_ob.remove_advanced("status", 'Continuous');
808                // //then aesthetically
809                // continuousButton.removeClass('input-active');
810
811                // continuousButton
812                //     .addClass('disabled').attr('aria-disabled', 'true')
813                //     .attr('title', 'This option is unavailable when a deadline is selected.');
814
815                $("#relative-deadline-type").val(ob['type']);
816                $("#relative-deadline-direction").val(ob['direction']);
817                $("#relative-deadline-count").val(ob['count']);
818                $("#relative-deadline-unit").val(ob['unit']);
819            });
820
821            $('.button-change-deadline-mode .relative-option').trigger('click');
822        }
823    },
824    set_subject: function (el, control) {
825        var selected_categories = $(".js-category-items");
826
827        $.each(control.query_ob.advanced['subject'], function (i, v) {
828            selected_categories.selected_categories("add_element", i, false);
829        });
830
831    },
832    set_sponsors: function (el, control) {
833        var selected_sponsors = $(".js-sponsor-items");
834        $.each(control.query_ob.advanced['sponsors'], function (i, v) {
835            selected_sponsors.selected_sponsors("add_element", i, false);
836        });
837
838    },
839    set_date: function (el, control) {
840        if ("earliest_submission" in control.query_ob.advanced) {
841            $('.button-change-deadline-mode .absolute-option').trigger('click');
842            $.each(control.query_ob.advanced["earliest_submission"], function (i, v) {
843                if (v == ">") {
844                    var date = control.convertQueryToLocalDate(i);
845                    $("#date-before").val(date).trigger("change");
846                    $('#date-before').attr('old_date', i);
847                } else if (v == "<") {
848                    i = Utils.addDayToString(i, -1);
849                    var date = control.convertQueryToLocalDate(i)
850                    $("#date-after").val(date).trigger("change");
851                    $('#date-after').attr('old_date', Utils.addDayToString(i, 1));
852                }
853                var minDate = new Date(control.dateTimeConvertable($("#date-before")));
854                var maxDate = new Date(control.dateTimeConvertable($('#date-after')));
855                $("#date-before").duet_date_picker("option", "maxDate", maxDate);
856                $("#date-after").duet_date_picker("option", "minDate", minDate);
857            });
858            el.date_filter("add_clear", $("#date-before"));
859        }
860    },
861    convertQueryToLocalDate: function (date) {
862        var format = gettext("date_format_place_holder");
863        return moment(date).format(format);
864    },
865    set_advanced_input: function (el, control, attr_for) {
866        jQuery.each(el.find(".js-advanced-input"), function (idx, elm) {
867            var val = $(elm).val();
868            if (control.query_ob.advanced[attr_for] && control.query_ob.advanced[attr_for][val]) {
869                var operator = control.query_ob.advanced[attr_for][val];
870
871                if (operator == '!=') {
872                    $(elm).addClass('input-negative').attr('aria-pressed', 'true');
873                    $(elm).siblings('.advanced-input-control-negative').addClass('active').attr('aria-pressed', 'true');
874                } else if (operator == '=') {
875                    $(elm).addClass('input-active').attr('aria-pressed', 'true');
876                    $(elm).siblings('.advanced-input-control-positive').addClass('active').attr('aria-pressed', 'true');
877                }
878            }
879        });
880    },
881    /*set_advanced: function(el, control, attr_for) {
882        el.find(".advanced_input").each(function(){
883            var input_el = $(this).find("input.advanced_input_button");
884            var input_val = input_el.val();
885            if (input_val in control.query_ob.advanced[attr_for]) {
886                el.advanced_filter("toggle_advanced_selected", input_el);
887
888                var operator = control.query_ob.advanced[attr_for][input_val];
889                $(this).find("select.advanced_input_select").data("selectBox-selectBoxIt").selectOption(operator);
890            }
891        });
892    },
893    set_basic: function(el, control, attr_for) {
894        el.find(".advanced_input_basic").each(function(){
895            var input_el = $(this);
896            var input_val = input_el.val();
897            if (input_val in control.query_ob.advanced[attr_for]) {
898                el.advanced_filter("toggle_basic_selected", input_el);
899            }
900        });
901    }
902    set_locations: function(el, control){
903        if ( typeof(control.query_ob.advanced['grant_location']) == 'undefined' ){
904            return;
905        }
906        for ( var location in control.query_ob.advanced['grant_location'] ){
907            control.locationFilter.addFilter(location, control.query_ob.advanced['grant_location'][location]);
908        }
909    },*/
910    load_applicant_locations: function (control) {
911        control.applicantLocationFilter.load();
912    },
913    load_activity_locations: function (control) {
914        control.activityLocationFilter.load();
915    },
916    load_amount: function (control) {
917        control.amountFilter.load();
918    },
919    load_cfda: function (control) {
920        control.cfdaFilter.load();
921    },
922    load_applicant_type: function (control) {
923        control.applicantTypeFilter.load();
924    },
925    load_sponsor: function (control) {
926        control.sponsorFilter.load();
927    },
928    load_sponsor_country: function (control) {
929        control.sponsorCountryFilter.load();
930    },
931    load_citizenship: function (control) {
932        control.citizenshipFilter.load();
933    },
934    dateTimeConvertable: function (date_picker) {
935        //when access datepicker without choosing date it is default to 1/1/1970 when new date a null date
936        if (typeof date_picker === "string") {
937            var format = getRegionDateFormat(); // get local format
938            return moment(date_picker, format).format("MM/DD/YYYY"); // convert it back to en-us for search
939        }else if (date_picker instanceof jQuery && typeof date_picker.duet_date_picker === "function"){
940            var datepicker_string = date_picker.duet_date_picker("getDate") || "";
941            if (!datepicker_string) return datepicker_string;
942            var format = date_picker.next("duet-date-picker").attr("format");
943            return moment(datepicker_string, format).format("MM/DD/YYYY"); // convert it back to en-us for search
944        }else {
945            return ""
946        }
947    },
948});
949
950/*Search Box*/
951MainSearchOpts.extend("SearchBox", {
952    init: function (el, opts) {
953        this._super(el, opts);
954
955        this.extra_search = $(".js-extra-search");
956        this.primary_search = $("#keyword-in");
957
958        /*$(".search_input").one("keypress focus click", function(){
959            if ($(this).attr("default") == "off") {
960                return true;
961            }
962            $(this).attr("default", "off");
963            $(this).val("");
964        });*/
965        this._sanitizeTextField = function () {
966            jQuery.each(this.element.find(".js-search-input"), function (idx, elm) {
967                var val = jQuery.trim($(elm).val());
968                if (val === '') {
969                    $(elm).val("");
970                }
971            });
972        }
973
974        // this._check = function(){
975        //     let check = 0;
976        //     jQuery.each( this.element.find(".js-search-input"), function(idx, elm){
977        //         var val = jQuery.trim( $(elm).val() );
978        //         if ( val === '' ){
979        //             $(elm).val("");
980        //         } else {
981        //             check = 1;
982        //         }
983        //     });
984        //     if ( check==0 ) {
985        //         this.element.toggleClass('advanced');
986        //     }
987        //     this.element.toggleClass('advanced');
988        // }
989    },
990    ".js-search-advanced-toggle click": function (el, ev) {
991        this.element.toggleClass('advanced');
992    },
993    ".js-search-submit click": function (el, ev) {
994        console.log('search submit');
995        this._sanitizeTextField();
996        this._fireSearchKeywordEvent();
997        this.query();
998    },
999    '.js-search-input keypress': function (el, ev) {
1000        if (ev.keyCode == 13) {
1001            // this._check();
1002            this._sanitizeTextField();
1003            this._fireSearchKeywordEvent();
1004            this.query();
1005        }
1006    },
1007    _fireSearchKeywordEvent: function () {
1008        var keywordsVal = [];
1009        jQuery.each($('.js-search-input'), function (idx, el) {
1010            var searchVal = jQuery.trim($(el).val());
1011            var type = $(el).attr('ktype');
1012            if (searchVal.length > 0) {
1013                keywordsVal.push(type + ' - ' + searchVal);
1014            }
1015        });
1016        if (keywordsVal.length > 0) {
1017            _trackGSEvent(
1018                '2.0-Search',
1019                'search_by_keyword',
1020                keywordsVal.join(' === ')
1021            );
1022        }
1023    },
1024    ".js-action-reset click": function (el, ev) {
1025        //This will remove current search results
1026        // this.set_empty();
1027        //This will remove all search things.
1028        this.query_ob.set_default();
1029        $('.suggested-keyword-old-ctn').empty();
1030        // Check if has window.smartSearchQuery then reset it
1031        if (typeof(window.smartSearchQuery)!=='undefined') {
1032            window.smartSearchQuery.setVal("");
1033            // Check if more operator has been hidden
1034            if ($('.more-operator').hasClass('hidden')) {
1035                window.smartSearchQuery.refreshView();
1036            }
1037        }
1038        if (typeof(window.triggerTransfer)!=='undefined') {
1039            window.triggerTransfer = true;
1040        }
1041        $("#smart-search-prompt").val("");
1042        // Code here
1043        $(".suggested-ctn").show();
1044
1045        //This will render default view
1046        $("body").query_loader('render');
1047
1048        _trackGSEvent(
1049            '2.0-Search',
1050            'reset_search',
1051            ''
1052        );
1053
1054    }
1055
1056});
1057/*Date Filter*/
1058MainSearchOpts.extend("DateFilter", {
1059    init: function (el, opts) {
1060        this._super(el, opts);
1061        this.DEFAULT_DATEPICKER_VAL = "";
1062        var elements = {
1063            'btnChangeMode': '.button-change-deadline-mode .mode-options',
1064            'btnModeAbsolute': '.button-change-deadline-mode .absolute-option',
1065            'btnModeRelative': '.button-change-deadline-mode .relative-option',
1066            'panelAbsoluteDeadline': '.panel-deadline-absolute',
1067            'panelRelativeDeadline': '.panel-deadline-relative',
1068            'deadlineType': '#relative-deadline-type',
1069            'deadlineDirection': '#relative-deadline-direction',
1070            'deadlineCount': '#relative-deadline-count',
1071            'deadlineUnit': '#relative-deadline-unit',
1072            'deadline.applyFilter': '.button-refresh-filter-deadline',
1073            'deadline.resetFilter': '.button-reset-filter-relative-deadline'
1074
1075        }
1076        function elm(name) {
1077            return $(elements[name]);
1078        }
1079
1080        var control = this;
1081        elm('btnModeAbsolute').on('click', function () {
1082            if (elm('btnModeAbsolute').hasClass('active'))
1083                return;
1084            elm('btnModeAbsolute').removeClass('btn-default').addClass('active btn-dark-blue');
1085            elm('btnModeRelative').removeClass('active btn-dark-blue').addClass('btn-default');
1086            elm('panelAbsoluteDeadline').removeClass('hidden');
1087            elm('panelRelativeDeadline').addClass('hidden');
1088            control.clear_relative_deadline(true, true);
1089            // $('.status-filter-Continuous')
1090            //     .removeClass('disabled')
1091            //     .removeAttr('aria-disabled')
1092            //     .removeAttr('title');
1093            // filter date
1094            var dateBefore = control.dateTimeConvertable($('#date-before')), dateAfter = control.dateTimeConvertable($('#date-after'));
1095            if (dateBefore) {
1096                control.query_ob.add_advanced("earliest_submission", dateBefore, '>');
1097                // control.query_ob.remove_advanced("status", 'Continuous');
1098                // $('.status-filter-Continuous').removeClass('input-active');
1099                // $('.status-filter-Continuous')
1100                //     .addClass('disabled').attr('aria-disabled', 'true')
1101                //     .attr('title', 'This option is unavailable when a deadline is selected.');
1102            }
1103            if (dateAfter) {
1104                dateAfter = Utils.addDayToString(dateAfter, 1);
1105                control.query_ob.add_advanced("earliest_submission", dateAfter, '<');
1106                // control.query_ob.remove_advanced("status", 'Continuous');
1107                // $('.status-filter-Continuous').removeClass('input-active');
1108                // $('.status-filter-Continuous')
1109                //     .addClass('disabled').attr('aria-disabled', 'true')
1110                //     .attr('title', 'This option is unavailable when a deadline is selected.');
1111            }
1112            control.query();
1113
1114        });
1115        elm('btnModeRelative').on('click', function () {
1116            if (elm('btnModeRelative').hasClass('active'))
1117                return;
1118            elm('btnModeRelative').removeClass('btn-default').addClass('active btn-dark-blue');
1119            elm('btnModeAbsolute').removeClass('active btn-dark-blue').addClass('btn-default');
1120            elm('panelRelativeDeadline').removeClass('hidden');
1121            elm('panelAbsoluteDeadline').addClass('hidden');
1122            control.clear_input(true, true);
1123            elm('deadlineCount').removeClass('has-error-count');
1124            // $('.status-filter-Continuous')
1125            //     .removeClass('disabled')
1126            //     .removeAttr('aria-disabled')
1127            //     .removeAttr('title');
1128            elm('deadline.applyFilter').trigger('click', true);
1129        });
1130        elm('deadlineType').on('change', function () {
1131            var valueType = elm('deadlineType').val();
1132            if (valueType == 'from') {
1133                elm('deadlineDirection').val('last').attr('disabled', 'disabled');
1134            } else if (valueType == 'after') {
1135                elm('deadlineDirection').val('next').attr('disabled', 'disabled');
1136            } else {
1137                elm('deadlineDirection').removeAttr('disabled');
1138            }
1139        });
1140        elm('deadlineUnit').on('change', function () {
1141            var valueCount = $.trim(elm('deadlineCount').val()),
1142                valueUnit = elm('deadlineUnit').val();
1143            control.set_max_input_relative_deadline();
1144            if (valueUnit == 'month' && valueCount > 120) {
1145                elm('deadlineCount').val(120);
1146            }
1147            if (valueUnit == 'week' && valueCount > 520) {
1148                elm('deadlineCount').val(520);
1149            }
1150            if (valueUnit == 'day' && valueCount > 3650) {
1151                elm('deadlineCount').val(3650);
1152            }
1153        });
1154        elm('deadlineCount').on('input', function () {
1155            elm('deadlineCount').removeClass('has-error-count');
1156            elm('deadlineCount').tooltip('disable');
1157        });
1158
1159        elm('deadline.applyFilter').on('click', function (e, load) {
1160            var valueCount = Number($.trim(elm('deadlineCount').val()));
1161            control.set_max_input_relative_deadline();
1162
1163            if (valueCount && Number.isInteger(Number(valueCount)) && Number(valueCount) > 0) {
1164                var valueType = elm('deadlineType').val(),
1165                    valueDirection = elm('deadlineDirection').val(),
1166                    valueUnit = elm('deadlineUnit').val();
1167                if (valueUnit == 'month' && valueCount > 120) {
1168                    valueCount = 120;
1169                }
1170                if (valueUnit == 'week' && valueCount > 520) {
1171                    valueCount = 520;
1172                }
1173                if (valueUnit == 'day' && valueCount > 3650) {
1174                    valueCount = 3650;
1175                }
1176                elm('deadlineCount').val(valueCount);
1177                var values = { 'type': valueType, 'direction': valueDirection, 'count': valueCount, 'unit': valueUnit };
1178                control.query_ob.add_advanced("relative_deadline", values, '=');
1179
1180                // control.clear_input(true, true);
1181
1182                var continuousButton = $('.status-filter-Continuous');
1183
1184                if (control.query_ob.advanced && control.query_ob.advanced['status']) {
1185                    if (Object.keys(control.query_ob.advanced['status']).length == 1 && control.query_ob.advanced['status']['Continuous']){
1186                        //remove logically
1187                        control.query_ob.remove_advanced("status", 'Continuous');
1188                        //then aesthetically
1189                        continuousButton.removeClass('input-active');
1190                    }
1191                }
1192
1193                elm('deadlineCount').removeClass('has-error-count');
1194                control.query();
1195
1196            } else if (!load) {
1197                // control.clear_relative_deadline(true);
1198                elm('deadlineCount').addClass('has-error-count');
1199                elm('deadlineCount').attr('data-original-title', 'The number of time units must be a positive integer!');
1200                elm('deadlineCount').tooltip('enable');
1201            } else {
1202                control.query();
1203            }
1204
1205        });
1206
1207        elm('deadline.resetFilter').on('click', function () {
1208            control.clear_relative_deadline(true);
1209        });
1210
1211
1212        $('#date-filter .js-date-input').duet_date_picker({
1213            dateFormat: getRegionDateFormat(),
1214            allowClear: false,
1215            onChange: function(date) {
1216                //since we did some prevention, and add 1 to the right end side, the date value will never coincide
1217                date = control.dateTimeConvertable(date)
1218
1219                var el = $(this);
1220                var old_date = el.attr("old_date");
1221
1222                var operator = el.attr('id') === 'date-before' ? '>' : '<';
1223
1224                //in "<" case, we need to plus 1 day into date value, to really include that day into the search
1225                if (operator === '<') {
1226                    date = Utils.addDayToString(date, 1);
1227                    var anotherElm = $('#date-before');
1228                } else {
1229                    var anotherElm = $('#date-after');
1230                }
1231                var validDate = control.isValidDateMoment(anotherElm.val());
1232                if(!validDate){
1233                    control.query_ob.remove_advanced("earliest_submission");
1234                    anotherElm.val(null);
1235                    anotherElm.trigger('autocomplete_selected');
1236                }
1237
1238                if (typeof (old_date) !== 'undefined' && old_date !== "") {
1239                    //only if it is the same kind of operator
1240                    control.query_ob.remove_advanced("earliest_submission", old_date);
1241                }
1242
1243                el.attr("old_date", date);
1244                control.clear_relative_deadline(true, true);
1245
1246                control.query_ob.add_advanced("earliest_submission", date, operator);
1247                control.add_clear(el);
1248
1249                //when we query with both continuous status, and deadline
1250                //the continuous status will be ignored
1251                //we wouldn't need to change anything on logic side
1252                //but we should show something that is more explicit to users
1253                //deactivate it if it is active, then disable the button
1254
1255                var continuousButton = $('.status-filter-Continuous');
1256
1257                if (control.query_ob.advanced && control.query_ob.advanced['status']) {
1258                    if (Object.keys(control.query_ob.advanced['status']).length == 1 && control.query_ob.advanced['status']['Continuous']){
1259                        //remove logically
1260                        control.query_ob.remove_advanced("status", 'Continuous');
1261                        //then aesthetically
1262                        continuousButton.removeClass('input-active');
1263                    }
1264                }
1265
1266                // continuousButton
1267                //     .addClass('disabled').attr('aria-disabled', 'true')
1268                //     .attr('title', 'This option is unavailable when a deadline is selected.');
1269
1270                control.query();
1271                $(this).trigger('autocomplete_selected');
1272            }
1273        });
1274
1275        //if min date is set, max date must be higher
1276        $("#date-before").on('autocomplete_selected', function () {
1277            var minDate = control.dateTimeConvertable($(this));
1278            $("#date-after").duet_date_picker("option", "minDate", new Date(minDate));
1279            _trackGSEvent(
1280                '2.0-Search',
1281                'deadline_lower',
1282                minDate
1283            );
1284        });
1285
1286        //if max date is set, min date must be lower
1287        $("#date-after").on('autocomplete_selected', function () {
1288            var maxDate = control.dateTimeConvertable($(this));
1289            $("#date-before").duet_date_picker("option", "maxDate", new Date(maxDate));
1290            _trackGSEvent(
1291                '2.0-Search',
1292                'deadline_upper',
1293                maxDate
1294            );
1295        });
1296        //click on the clean deadline button
1297        $('#date-filter .js-clear-input > button').on('click', function (el) {
1298            control.clear_input(true);
1299        });
1300    },
1301    isValidDateMoment: function (dateString) {
1302        // moment(dateString, format, strict_parsing_boolean)
1303        // Passing true as the third argument enables strict parsing.
1304        var format = gettext("date_format_place_holder") || "MM/DD/YYYY"; // get local format
1305        return dateString.length === format.length && moment(dateString, format, true).isValid();
1306    },
1307    set_max_input_relative_deadline: function () {
1308        var valueUnit = $('#relative-deadline-unit').val();
1309        if (valueUnit == 'month') {
1310            $('#relative-deadline-count').attr('max', 120);
1311        }
1312        if (valueUnit == 'week') {
1313            $('#relative-deadline-count').attr('max', 520);
1314        }
1315        if (valueUnit == 'day') {
1316            $('#relative-deadline-count').attr('max', 3650);
1317        }
1318
1319    },
1320    clear_input: function (refresh, relativeDeadline) {
1321        if (!relativeDeadline) {
1322            $('#date-filter .js-clear-input').addClass('hidden');
1323            $("#date-filter .js-date-input").duet_date_picker("option", "maxDate", null);
1324            $("#date-filter .js-date-input").duet_date_picker("option", "minDate", null);
1325            $('#date-filter .js-date-input').val(this.DEFAULT_DATEPICKER_VAL).trigger("change");
1326
1327            //no more deadlines, we can restore the status button with Continuous value
1328            // $('.status-filter-Continuous')
1329            //     .removeClass('disabled')
1330            //     .removeAttr('aria-disabled')
1331            //     .removeAttr('title');
1332        }
1333
1334        if (refresh) {
1335            this.query_ob.remove_advanced("earliest_submission");
1336            if (!relativeDeadline)
1337                this.query();
1338        }
1339    },
1340    clear_relative_deadline: function (refresh, dateDeadline) {
1341        if (!dateDeadline) {
1342            $('#relative-deadline-type').val('within');
1343            $('#relative-deadline-direction').val('next');
1344            $('#relative-deadline-count').val('');
1345            $('#relative-deadline-unit').val('month');
1346            $('#relative-deadline-direction')
1347                .removeAttr('disabled');
1348            // $('.status-filter-Continuous')
1349            //     .removeClass('disabled')
1350            //     .removeAttr('aria-disabled')
1351            //     .removeAttr('title');
1352            $('#relative-deadline-count').removeClass('has-error-count');
1353        }
1354        if (refresh) {
1355            this.query_ob.remove_advanced("relative_deadline");
1356            if (!dateDeadline)
1357                this.query();
1358        }
1359    },
1360    add_clear: function (base_elem) {
1361        var formItemElm = $(base_elem).closest('#date-filter');
1362        var clearElm = formItemElm.find('.js-clear-input');
1363
1364        clearElm.removeClass('hidden');
1365    },
1366    dateTimeConvertable: function (date_picker) {
1367        //when access datepicker without choosing date it is default to 1/1/1970 when new date a null date
1368        if (typeof date_picker === "string") {
1369            var format = gettext("date_format_place_holder") || "MM/DD/YYYY"; // get local format
1370            return moment(date_picker, format).format("MM/DD/YYYY"); // convert it back to en-us for search
1371        }else if (date_picker instanceof jQuery && typeof date_picker.duet_date_picker === "function"){
1372            var datepicker_string = date_picker.duet_date_picker("getDate") || "";
1373            if (!datepicker_string) return datepicker_string;
1374            var format = date_picker.next("duet-date-picker").attr("format");
1375            return moment(datepicker_string, format).format("MM/DD/YYYY"); // convert it back to en-us for search
1376        }else {
1377            return ""
1378        }
1379    },
1380});
1381
1382/*Category Filter*/
1383MainSearchOpts.extend("CategoryFilter", {
1384    init: function (el, opts) {
1385        this._super(el, opts);
1386
1387        this.picker = this.options.picker;
1388        this.selected = this.options.selected;
1389
1390        var control = this;
1391
1392        //Category picker stuff
1393        this.picker = $("#category-modal");
1394        this.picker.category_picker({
1395            "trunc_len": 30,
1396            "callback": function (lt) {
1397                control.query_ob.remove_advanced("subject");
1398                $.each(lt, function (i, v) {
1399                    control.query_ob.add_advanced("subject", i, "=");
1400                });
1401                control.query();
1402            }
1403        });//On select from picker box.
1404
1405        this.selected = $(".js-category-items");
1406        this.selected.selected_categories({
1407            "trunc_len": 30,
1408            "callback": function (val) {
1409                if (typeof (val) != 'undefined') {
1410                    control.query_ob.remove_advanced("subject", val);
1411                    control.query();
1412                }
1413            }
1414        });//On click to remove
1415
1416        var control = this;
1417        var searchbox = $(el).find("#categories");
1418
1419        searchbox.one("keypress focus click", function () {
1420            $(this).attr("default", "off");
1421            $(this).val("");
1422        });
1423
1424        searchbox.autocomplete({
1425            source: function (request, response) {
1426                $.getJSON("/search/db_autosuggest", { term: request.term, suggest_type: "category" }, response);
1427            },
1428            select: function (el, ui) {
1429                control.selected.selected_categories("add_element", ui.item.value);
1430                $(this).val('');
1431
1432                control.query_ob.add_advanced("subject", ui.item.value, "=");
1433                control.query();
1434
1435                _trackGSEvent(
1436                    '2.0-Search',
1437                    'category_search',
1438                    ui.item.value
1439                );
1440
1441                return false;
1442            },
1443            close: function (el, ui) {
1444                //$(this).val('');
1445                return false;
1446            },
1447            autoFocus: true
1448        });
1449
1450    },
1451    ".js-category-select click": function (el, ev) {
1452        if (this.picker.is(":visible")) {
1453            this.picker.category_picker("hide_picker");
1454        } else {
1455            var selected = $(".js-category-items").selected_categories("get_data").data('js-selected-items');
1456            this.picker.category_picker("show_picker", selected);
1457        }
1458    }
1459});
1460/*Sponsor Filter*/
1461/*MainSearchOpts.extend("SponsorFilter", {
1462    init: function(el, opts) {
1463        this._super(el, opts);
1464
1465        var control = this;
1466        var searchbox = $(el).find("#sponsors");
1467
1468        //Sponsor picker stuff
1469        this.picker = $("#sponsor-modal");
1470        this.picker.sponsor_picker({
1471            "trunc_len": 60,
1472            "callback": function(lt) {
1473                control.query_ob.remove_advanced("sponsors");
1474                $.each(lt, function(i, v){
1475                    control.query_ob.add_advanced("sponsors", i, "=");
1476                });
1477                control.query();
1478            }
1479        });
1480
1481        this.selected = $(".js-sponsor-items");
1482        this.selected.selected_sponsors({
1483            "trunc_len": 30,
1484            "callback": function(val) {
1485                if ( typeof(val) != 'undefined' ){
1486                    control.query_ob.remove_advanced("sponsors", val);
1487                    control.query();
1488                }
1489            }
1490        });
1491
1492        searchbox.one("keypress focus click", function(){
1493            $(this).attr("default", "off");
1494            $(this).val("");
1495        });
1496
1497        searchbox.autocomplete({
1498            source: function(request, response) {
1499                $.getJSON("/search/db_autosuggest", { term: request.term, suggest_type: "sponsor" }, response);
1500            },
1501            select: function(el, ui) {
1502                control.selected.selected_sponsors("add_element", ui.item.value);
1503                $(this).val('');
1504                control.query_ob.add_advanced("sponsors", ui.item.value, "=");
1505                control.query();
1506
1507                _trackGSEvent(
1508                    '2.0-Search',
1509                    'sponsor_search',
1510                    ui.item.value
1511                );
1512                return false;
1513            },
1514            close: function(el, ui) {
1515                //$(this).val('');
1516                return false;
1517            },
1518            autoFocus: true
1519        });
1520    },
1521    ".js-sponsor-select click": function(el, ev){
1522        if (this.picker.is(":visible")) {
1523            this.picker.sponsor_picker("hide_picker");
1524        } else {
1525            var selected = $(".js-sponsor-items").selected_sponsors("get_data").data('js-selected-items');
1526            this.picker.sponsor_picker("show_picker", selected);
1527        }
1528    }
1529});*/
1530
1531
1532MainSearchOpts.extend("AdvancedFilter", {
1533    init: function (el, opts) {
1534        this._super(el, opts);
1535
1536        this.for_attr = this.element.attr("for");
1537        var control = this;
1538
1539        this.negative_support = opts.negative_support;
1540
1541        //negative filter enabled
1542        if (opts.negative_support === true) {
1543            this.element.find('.advanced-input-control-negative').on('click', function (event) {
1544                var controlEl = $(event.currentTarget);
1545                var el = controlEl.siblings('.advanced-input');
1546                control.query_ob.remove_advanced(control.for_attr, el.val());
1547
1548                if (!el.hasClass("input-negative")) {
1549                    control.query_ob.add_advanced(control.for_attr, el.val(), "!=");
1550                    control.fireEventAction(control.for_attr, false, el.val());
1551                }
1552                el.removeClass('input-active').toggleClass("input-negative");
1553
1554                controlEl.toggleClass('active');
1555
1556                if (controlEl.hasClass('active')) {
1557                    controlEl.attr('aria-pressed', 'true');
1558                } else {
1559                    controlEl.attr('aria-pressed', 'false');
1560                }
1561
1562                controlEl.siblings('.advanced-input-control-positive').removeClass('active').attr('aria-pressed', 'false');
1563                control.query();
1564            });
1565            this.element.find('.advanced-input-control-positive').on('click', function (event) {
1566                var controlEl = $(event.currentTarget);
1567                controlEl.siblings('.advanced-input').click();
1568            });
1569        }
1570    },
1571    ".advanced-input click": function (el, ev) {
1572        var control = this;
1573        if ($(el).hasClass('disabled')) {
1574            return;
1575        };
1576        this.query_ob.remove_advanced(this.for_attr, el.val());
1577        if (!el.hasClass("input-active")) {
1578            this.query_ob.add_advanced(this.for_attr, el.val(), "=");
1579            control.fireEventAction(control.for_attr, true, el.val());
1580        }
1581
1582        if (control.for_attr == "status"){
1583            control.onStatusAction(control);
1584        }
1585
1586        el.removeClass('input-negative').toggleClass("input-active");
1587
1588        if (control.negative_support === false) {
1589            if (el.hasClass('input-active')) {
1590                el.attr('aria-pressed', 'true');
1591            } else {
1592                el.attr('aria-pressed', 'false');
1593            }
1594        }
1595
1596        el.siblings('.advanced-input-control-negative').removeClass('active').attr('aria-pressed', 'false');
1597        el.siblings('.advanced-input-control-positive').toggleClass('active');
1598
1599        if (el.siblings('.advanced-input-control-positive').hasClass('active')) {
1600            el.siblings('.advanced-input-control-positive').attr('aria-pressed', 'true');
1601        } else {
1602            el.siblings('.advanced-input-control-positive').attr('aria-pressed', 'false');
1603        }
1604
1605        this.query();
1606    },
1607    ".advanced-input keypress": function (el, ev) {
1608        if (ev.which == 32 /* space */) {
1609            if ($(el).hasClass('disabled')) {
1610                return;
1611            };
1612            $(el).click();
1613        }
1614    },
1615    fireEventAction: function (forAttr, positive, value) {
1616        var eventName;
1617
1618        switch (forAttr) {
1619            case 'type':
1620                eventName = 'sponsor_type';
1621                break;
1622            case 'grant_types':
1623                eventName = 'grant_type';
1624                break;
1625            case 'status':
1626                eventName = 'status';
1627                break;
1628            case 'is_limited':
1629                eventName = 'submission';
1630                break;
1631            case 'applicant_types':
1632                eventName = 'applicant_type';
1633                break;
1634            // case 'grant_location':
1635            //     eventName = 'grant_location';
1636            // break;
1637        }
1638
1639        var eventSign = positive ? '_positive' : '_negative';
1640
1641        //2.4.1: trackers
1642        _trackGSEvent(
1643            '2.0-Search',
1644            eventName + eventSign,
1645            value
1646        );
1647    },
1648    onStatusAction: function(control){
1649        if (control.query_ob.advanced && control.query_ob.advanced['status']) {
1650            if (Object.keys(control.query_ob.advanced['status']).length == 1 && control.query_ob.advanced['status']['Continuous']){
1651                control.query_ob.remove_advanced("earliest_submission");
1652                $("#date-filter").date_filter("clear_input", true);
1653                $("#date-filter").date_filter("clear_relative_deadline", true);
1654            }
1655        }
1656    }
1657});
1658
1659MainSearchOpts.extend("PaginationHelper", {
1660    init: function (el, opts) {
1661        this._super(el, opts);
1662    },
1663    onPageClicked: function(e, page){
1664        if (typeof (window.searchQ) != 'undefined' && window.searchQ !== null) {
1665            return;
1666        }
1667        this.query_ob.is_change_page = true;
1668        this.query_ob.current_page = page;
1669        this.query(true, false);
1670        $('.search-pagination .paginator ul').not($(e.currentTarget)).bootstrapPaginator("setOptions", { currentPage: page });
1671    },
1672    update_page: function (hits, per_page) {
1673        var totalPageNum = Math.ceil(hits / per_page);
1674        var self = this;
1675        if (totalPageNum > 1) {
1676            $('.search-pagination .paginator ul').bootstrapPaginator({
1677                currentPage: this.query_ob.current_page,
1678                totalPages: totalPageNum,
1679                bootstrapMajorVersion: 3,
1680                onPageClicked: function(e, originalEvent, type, page){ self.onPageClicked(e, page) }.bind(this),
1681                pageUrl: '#search-main',
1682            });
1683            $('.paginator ul').removeClass("hidden");
1684        } else {
1685            $('.paginator ul').addClass("hidden");
1686        }
1687
1688        if (hits === 0) {
1689            this.element.addClass('hidden');
1690            return true;
1691        } else {
1692            this.element.removeClass('hidden');
1693        }
1694    }
1695});
1696
1697MainSearchOpts.extend("SaveAlertForm", {
1698    /*All these selectors should be at runtime so that we don't have to rebind the controller*/
1699    init: function (el, opts) {
1700        this._super(el, opts);
1701        var control = this;
1702
1703        /** @type {Object} define save mode */
1704        this.mode = {
1705            UPDATE: 'existing',
1706            NEW: 'new'
1707        };
1708        this.currentMode = this.mode.NEW;
1709
1710        /** @type {Object} defined message error / complete */
1711        this.feedbackMsg = {
1712            SUCCESS: 'Save updated!',
1713            ERROR: 'Save not updated! Please try again!',
1714            NEW_ERROR: 'Please select a name for your new search or update an existing one!',
1715            UPDATE_ERROR: 'Please select an existing search to update!'
1716        };
1717        /** @type {Object} define type of feedback message */
1718        this.feedbackMode = {
1719            SUCCESS: 'good',
1720            FAILURE: 'fail'
1721        };
1722
1723        this.triggerReload = false;
1724
1725        this.element.on("hidden.bs.modal", function () {
1726            $(this).find('.modal-content').html('');
1727            $(this).removeData('bs.modal');
1728            control.hideFeedback();
1729            if (control.triggerReload) {
1730                window.location = window.location.protocol + '//' + window.location.host + window.location.pathname;
1731            }
1732        });
1733    },
1734    'input[name="save-options"] change': function (el, ev) {
1735        this.hideFeedback();
1736        var mode = el.val();
1737        this.element.find('.js-options').removeClass('active');
1738        el.closest('.js-options').addClass('active');
1739        //if there is a mode change, reset frequency
1740        if (this.currentMode == this.mode.UPDATE && mode == this.mode.NEW) {
1741            this.element.find('.js-save-frequency').val("");
1742            this.element.find('input[name="ignore-no-grants"][value=0]').prop('checked', true);
1743        }
1744        $('input[name="ignore-updated-grants"][value="1"]').prop('checked', true);
1745        this.currentMode = mode;
1746        if (mode === this.mode.UPDATE) {
1747            this.updateFrequency(this.element.find('.js-saved-items'));
1748            $('.option-template').addClass('hidden');
1749        } else {
1750            this.element.find('.js-new-save-input').focus();
1751            $('.option-template').removeClass('hidden');
1752        }
1753    },
1754    //on focus on saved items select, choose correct options
1755    '.js-saved-items change': function (el, ev) {
1756        this.hideFeedback();
1757        this.element.find('input[name="save-options"][value=' + this.mode.UPDATE + ']').prop('checked', true);
1758        this.currentMode = this.mode.UPDATE;
1759        this.updateFrequency(this.element.find('.js-saved-items'));
1760        $('.option-template').addClass('hidden');
1761
1762        $('.js-new-save-input').val('');
1763    },
1764    '.js-save-frequency change': function (el, ev) {
1765        this.hideFeedback();
1766    },
1767    '.js-new-save-input input': function (el, ev) {
1768        var inputValue = $.trim(el.val());
1769        if (inputValue.length > 0) {
1770            this.hideFeedback();
1771            if (this.currentMode == this.mode.UPDATE) {
1772                this.element.find('.js-save-frequency').val("");
1773                this.element.find('input[name="ignore-no-grants"][value=0]').prop('checked', true);
1774            }
1775            this.element.find('input[name="save-options"][value=' + this.mode.NEW + ']').prop('checked', true);
1776            this.currentMode = this.mode.NEW;
1777            $('.option-template').removeClass('hidden');
1778        }
1779    },
1780
1781    '.js-new-save-input focus': function (el, ev) {
1782        this.hideFeedback();
1783    },
1784    updateFrequency: function (select_box) {
1785        var selected = $('option:selected', select_box);
1786
1787        if (selected.attr("set") == "1") {
1788            this.element.find('.js-save-frequency').val(selected.attr("freq"));
1789        } else {
1790            this.element.find('.js-save-frequency').val("");
1791        }
1792        if (selected.attr("value") != ''){
1793            this.element.find('input[name="ignore-no-grants"][value=' + (selected.attr("ignore_no_grants") == "1" ? "1" : "0") + ']').prop('checked', true);
1794            this.element.find('input[name="ignore-updated-grants"][value=' + (selected.attr("ignore_updated_grants") == "1" ? "1": "0") + ']').prop('checked', true);
1795        }
1796    },
1797    '.js-action-save-submit click': function (el, ev) {
1798        this.hideFeedback();
1799        var control = this;
1800
1801        var state = this.element.find("input[name='save-options']:checked").val();
1802
1803        var search_id = null;
1804        var search_name = null;
1805
1806        var search_params = this.query_ob.construct(false);
1807        var search_text = this.build_search_text();
1808        var frequency = this.element.find(".js-save-frequency").val();
1809        var ignore_no_grants = this.element.find('input[name="ignore-no-grants"]:checked').val();
1810        var sort_field = $('#sort option:selected').val();
1811        var sort_direction = $('#direction option:selected').val();
1812        var search_mode = window.isBasicMode ? 'basic' : 'advanced';
1813
1814        var ignore_updated_grant = this.element.find('input[name="ignore-updated-grants"]:checked').val();
1815        if (state == "new") {
1816            search_name = $.trim(this.element.find(".js-new-save-input").val());
1817            if (search_name.length === 0) {
1818                this.showFeedback(this.feedbackMsg.NEW_ERROR, this.feedbackMode.FAILURE);
1819                return false;
1820            }
1821            search_id = -1;
1822            //2.4.1: trackers
1823            _trackGSEvent(
1824                '2.0-Search',
1825                'save_search_submit',
1826                'New' + ' - ' + search_name + ' - ' + frequency
1827            );
1828        } else {
1829            search_id = $.trim(this.element.find('.js-saved-items option:selected').val());
1830            search_name = this.element.find('.js-saved-items option:selected').text();
1831
1832            if (search_id === "") {
1833                this.showFeedback(this.feedbackMsg.UPDATE_ERROR, this.feedbackMode.FAILURE);
1834                return false;
1835            }
1836
1837            //2.4.1: trackers
1838            _trackGSEvent(
1839                '2.0-Search',
1840                'save_search_submit',
1841                'Override' + ' - ' + search_name + ' - ' + frequency
1842            );
1843        }
1844
1845        $.ajax({
1846            type: "POST",
1847            url: "/user/update_search",
1848            data: {
1849                "solr_search": search_params['qs'],
1850                "search_text": search_text,
1851                "search_id": search_id,
1852                "search_name": search_name,
1853                "frequency": frequency,
1854                "ignore_no_grants": ignore_no_grants,
1855                "sort_field": sort_field,
1856                "sort_direction": sort_direction,
1857                "search_mode": search_mode,
1858                "ignore_updated_grant": ignore_updated_grant
1859            },
1860            dataType: "json",
1861            beforeSend: function () {
1862                control.element.find('.js-action-save-submit').attr('disabled', 'disabled');
1863            },
1864            success: function (response) {
1865                if (response.status == 1) {
1866                    if (state != "new") {
1867                        control.triggerReload = true;
1868                    }
1869                    control.showFeedback(response.msg, control.feedbackMode.SUCCESS);
1870                    setTimeout(function () {
1871                        control.element.modal('hide');
1872                    }, 2000);
1873                } else {
1874                    control.element.find('.js-action-save-submit').removeAttr('disabled');
1875                    control.showFeedback(response.error, control.feedbackMode.FAILURE);
1876                }
1877            },
1878            error: function () {
1879                control.element.find('.js-action-save-submit').removeAttr('disabled');
1880                control.showFeedback(control.feedbackMsg.ERROR, control.feedbackMode.FAILURE);
1881            }
1882        });
1883    },
1884    showFeedback: function (message, type) {
1885        this.hideFeedback();
1886        this.element.find('.js-alert-' + type).html(message).removeClass('hidden');
1887    },
1888    hideFeedback: function () {
1889        this.element.find('.js-alert').addClass('hidden');
1890    }
1891});
1892
1893MainSearchOpts.extend("LoadAlertForm", {
1894    /*All these selectors should be at runtime so that we don't have to rebind the controller*/
1895    init: function (el, opts) {
1896        this._super(el, opts);
1897        var control = this;
1898
1899        /** @type {Object} define type of feedback message */
1900        this.feedbackMode = {
1901            SUCCESS: 'good',
1902            FAILURE: 'fail'
1903        };
1904
1905        this.triggerReload = false;
1906
1907        this.element.on("loaded.bs.modal", function () {
1908            if (control.element.find('.js-saved-items option[is_default="1"]').length > 0) {
1909                control.element.find('.js-saved-items option[is_default="1"]').prop("selected", true);
1910                control.element.find('.js-saved-items').trigger('change');
1911            }
1912        });
1913
1914        this.element.on("hidden.bs.modal", function () {
1915            $(this).find('.modal-content').html('');
1916            $(this).removeData('bs.modal');
1917            control.hideFeedback();
1918            if (control.triggerReload) {
1919                window.location = window.location.protocol + '//' + window.location.host + window.location.pathname;
1920            }
1921        });
1922    },
1923    //on focus on saved items select, choose correct options
1924    '.js-saved-items change': function (el, ev) {
1925        this.hideFeedback();
1926        var control = this,
1927            select_box = this.element.find('.js-saved-items option:selected'),
1928            value = select_box.attr('value'),
1929            is_default = select_box.attr('is_default');
1930        if (value != '') {
1931            this.element.find('.js-action-load-submit').removeAttr('disabled');
1932            this.element.find('.default-saved-search').removeAttr('disabled');
1933            this.element.find('.default-saved-search').prop('checked', is_default === '1');
1934            this.element.find('.set-as-default-container').removeClass('hidden');
1935        } else {
1936            this.element.find('.js-action-load-submit').attr('disabled', 'disabled');
1937            this.element.find('.default-saved-search').prop('checked', false);
1938            this.element.find('.default-saved-search').attr('disabled', 'disabled');
1939            this.element.find('.set-as-default-container').addClass('hidden');
1940        }
1941    },
1942    '.default-saved-search click': function (el, ev) {
1943        this.hideFeedback();
1944        var control = this,
1945            select_box = this.element.find('.js-saved-items option:selected'),
1946            value = select_box.attr('value'),
1947            is_default = el.prop('checked');
1948
1949        $.ajax({
1950            type: "POST",
1951            url: "/user/mark_default_alert",
1952            data: {
1953                search_id: value,
1954                is_default: is_default ? '1' : '0'
1955            },
1956            dataType: "json",
1957            beforeSend: function () {
1958                el.attr('disabled', 'disabled');
1959            },
1960            success: function (response) {
1961                el.removeAttr('disabled');
1962                if (response.code == '1') {
1963                    control.triggerReload = true;
1964                    control.showFeedback(response.msg, control.feedbackMode.SUCCESS);
1965                    if (is_default) {
1966                        control.element.find('.js-saved-items option').each(function () {
1967                            var option = $(this);
1968                            option.text(option.text().replace(' (Set as Default)', ''));
1969                            option.attr('is_default', '0');
1970                        });
1971                        control.element.find('.js-saved-items option:selected').each(function () {
1972                            var option = $(this);
1973                            option.text(option.text() + ' (Set as Default)');
1974                            option.attr('is_default', '1');
1975                        });
1976                    } else {
1977                        control.element.find('.js-saved-items option:selected').each(function () {
1978                            var option = $(this);
1979                            option.text(option.text().replace(' (Set as Default)', ''));
1980                            option.attr('is_default', '0');
1981                        });
1982                    }
1983                } else {
1984                    el.prop('checked', !is_default);
1985                    control.showFeedback(response.error, control.feedbackMode.FAILURE);
1986                }
1987            },
1988            error: function () {
1989                el.removeAttr('disabled');
1990                el.prop('checked', !is_default);
1991                control.showFeedback('Your request could not be handled at the moment! Please try again in a short while!', control.feedbackMode.FAILURE);
1992            }
1993        });
1994    },
1995    '.js-action-load-submit click': function (el, ev) {
1996
1997        var control = this,
1998            select_box = this.element.find('.js-saved-items option:selected'),
1999            keywords = select_box.attr('keywords'),
2000            query = select_box.attr('query'),
2001            is_default = select_box.attr('is_default'),
2002            sort_field = select_box.attr('sort_field');
2003            sort_direction = select_box.attr('sort_direction');
2004            search_mode = select_box.attr('search_mode');
2005            if (search_mode === 'advanced'){
2006                window.isBasicMode = false;
2007            }
2008            else{
2009                window.isBasicMode = true;
2010            }
2011
2012        _trackGSEvent(
2013            '2.0-Search',
2014            'load_search_submit',
2015            select_box[0].innerText + ' - ' + is_default
2016        );
2017        $('.suggested-keyword-old-ctn').empty();
2018        if (typeof(window.smartSearchQuery)!=='undefined' && typeof(window.smartSearchQuery.value)!=='undefined') {
2019            window.smartSearchQuery.setVal("");
2020            // Check if more operator has been hidden
2021            if ($('.more-operator').hasClass('hidden')) {
2022                window.smartSearchQuery.refreshView();
2023            }
2024
2025        }
2026        this.query_ob.override_default(!!query ? JSON.parse(query) : [], is_default);
2027        this.query_ob.add_searchtext(keywords, is_default);
2028        if (sort_field) {
2029            this.query_ob.add_sortby(sort_field);
2030        }
2031        if (sort_direction){
2032            this.query_ob.add_sortDirection(sort_direction);
2033        }
2034        if (!control.triggerReload) {
2035            $("body").query_loader('render');
2036        }
2037        control.element.modal('hide');
2038    },
2039    showFeedback: function (message, type) {
2040        this.hideFeedback();
2041        this.element.find('.js-alert-' + type).html(message).removeClass('hidden');
2042    },
2043    hideFeedback: function () {
2044        this.element.find('.js-alert').addClass('hidden');
2045    }
2046});
2047
2048$.Controller('SaveTooltips', {
2049    init: function (el, opts) {
2050        var user_id = opts['user_id'];
2051        if (user_id && user_id != "None" && user_id != "-1")
2052            this.content = "Click here to add this grant to your favorites.";
2053        else
2054            this.content = "Please log in or sign-up to add this grant to your favorites.";
2055        //czd_tooltip($(el), {"content":this.content});
2056    }
2057});
2058
2059$.Controller('ViewTooltips', {
2060    init: function (el, opts) {
2061        var user_id = opts['user_id'];
2062        if (user_id && user_id != "None" && user_id != "-1")
2063            this.content = "This grant is in your favorites. You can go to your account to view it.  <br>Or you can click on this star to remove this grant from your favorites.";
2064        else
2065            this.content = "Please log in or sign-up to view this grant in your favorites.";
2066        //czd_tooltip($(el), {"content":this.content});
2067    }
2068});
2069
2070$.Controller('CommentButton',
2071{
2072    flag: "",
2073    comment: "",
2074    offset: "",
2075    field: "",
2076    display: "",
2077    grant_id: "",
2078    grant_title: "",
2079    init: function(el) {
2080        this.flag = el;
2081        this.field = $(el).attr("field");
2082        this.display = $(el).attr("display");
2083        this.grant_id = $(el).attr("grant_id");
2084        this.grant_title = $(el).parents('div.description').siblings('div.title').children('a.grant-url').text();
2085        this.offset = $(el).offset();
2086    },
2087    click: function(el) {
2088        if ( $(el).hasClass('disabled') ){
2089            return;
2090        }
2091        this.comment = $(el).attr("comment");
2092        var last_modified = $(el).attr("last_modified");
2093        $('#comment-modal').comment_form({'elm': el, 'comment':this.comment, 'field':this.field, 'display':this.display, 'position':this.offset, 'flag':this.flag, 'grant_id':this.grant_id, 'grant_title':this.grant_title, 'last_modified':last_modified});
2094    }
2095});
2096
2097$.Controller('CommentForm',
2098{
2099    field : "",
2100    form: "",
2101    flag: "",
2102    grant_id: "",
2103    grant_title: "",
2104    init : function(el, opts) {
2105        var me = this;
2106        this.form = el;
2107        this.field = opts['field'];
2108        this.flag = opts['flag'];
2109        this.grant_id = opts['grant_id'];
2110        this.grant_title = opts['grant_title'];
2111        if(typeof opts['last_modified'] != 'undefined' && opts['last_modified'] !== "") {
2112            this.element.find(".js-last-submitted").text('Submitted on '+opts['last_modified'].split(' ')[0]);
2113        } else {
2114            this.element.find(".js-last-submitted").text('');
2115        }
2116        this.element.find(".js-comment-content").val(opts['comment']);
2117        this.element.find(".js-content-type").text(opts['display']);
2118
2119        this.element.one('hidden.bs.modal', function(){
2120            $(this).comment_form('destroy');
2121        });
2122
2123        this.element.one('shown.bs.modal', function () {
2124            //hide error
2125            var errorElm = me.element.find('.js-report-error');
2126            errorElm.addClass('hidden');
2127            // focus on comment-content
2128            var inputElm = me.element.find('.js-comment-content');
2129            inputElm.focus();
2130            inputElm[0].setSelectionRange(inputElm.val().length, inputElm.val().length);
2131        })
2132
2133        this.element.data('js-refocus-target', opts['elm']).modal('show');
2134    },
2135    ".js-action-comment-submit click": function (el, opts) {
2136        // they're going out of scope in that anon class
2137        var rootElm = this.element;
2138        var form = this.form;
2139        var field = this.field;
2140        var flag = this.flag;
2141        var grant_id = this.grant_id;
2142        var grant_title = this.grant_title;
2143        var inputElm = rootElm.find('.js-comment-content');
2144        var errorElm = rootElm.find('.js-report-error');
2145
2146        //hide error
2147        errorElm.addClass('hidden');
2148
2149        var val = jQuery.trim( inputElm.val() );
2150
2151        if ( val === '' ){
2152            errorElm.html('Your report must not be blank!').removeClass('hidden');
2153            setTimeout(() => {
2154                errorElm.addClass('hidden');
2155            }, 1000);
2156            return;
2157        }
2158
2159        $.ajax({
2160            type: 'POST',
2161            url: '/grant/comment',
2162            data: {
2163                "grant_id" : grant_id,
2164                "field" : field,
2165                "comment": inputElm.val(),
2166                "grant_title": grant_title
2167            },
2168            success: function(response) {
2169                //alert(response);
2170                $(flag).attr("last_modified", response.date);
2171                $(flag).attr("comment", inputElm.val());
2172                $(flag).attr("src", "/public/img/report_existing.png");
2173                rootElm.modal('hide');
2174            }
2175        });
2176    }
2177});
2178
2179function log_export() {
2180    var timediff = new Date().getTime() - _curtime;
2181    _gaq.push(['_trackEvent', 'postsearch', 'export_time', timediff.toString()]);
2182    _gaq.push(['_trackEvent', 'postsearch', 'export_preclick', query.get_results_clicked()]);
2183    //Can get export total by looking at unique number of either of these
2184}
2185
2186//Controller to handle the toggle of search filters
2187/**
2188 * View for SearchFilterToggle
2189 * TODO: REmove this
2190 * @return SearchFilterToggle an instance of the view class
2191 */
2192var SearchFilterToggle = SearchFilterToggle || function (query) {
2193
2194    var me = this;
2195
2196    me.query = query;
2197
2198    /* Element list */
2199    var elements = {
2200        'sponsors': '#sponsor-filter',
2201        'sponsor_id': '#sponsor-filter',
2202        'categories': '#category-filter',
2203        'amount': '#amount-filter',
2204        'status': '#status-filter',
2205        'deadline': '#date-filter',
2206        'sponsorType': '#sponsor-type-filter',
2207        'grantType': '#type-filter',
2208        'applicantLocations': '#location-filter',
2209        'activityLocations': '#activity-location-filter',
2210        'submission': '#submission-filter',
2211        'applicantType': '#applicant-filter',
2212        'cfdaNum': '#cfda-filter',
2213        'citizenship': '#citizenship-filter',
2214        'sponsorCountry': '#sponsor-country-filter',
2215
2216        //generalization
2217        'filter': '.js-filter-item',
2218        'filter.close': '.toggle-icon-close',
2219        'filter.open': '.toggle-icon-open',
2220        'filter.toggle': '.accordion-toggle',
2221        'excludeLocation': 'excluded-location',
2222        'excludeSponsor': 'excluded-sponsor-list',
2223        'excludeActivityLocation': 'activity-excluded-location',
2224        'excludeCitizenship': 'excluded-citizenships'
2225    };
2226
2227    /* Element getter */
2228    function elm(name) {
2229        return $(elements[name]);
2230    }
2231
2232    /**
2233     * contains all event handlers for the view
2234     * @type {Object}
2235     */
2236    var eventHandlers = {
2237    };
2238
2239    /**
2240     * map of filter name, used for knowing which filter group we should update.
2241     * @type {Object}
2242     */
2243    var map = {
2244        'sponsors': 'sponsors',
2245        'sponsor_id': 'sponsor_id',
2246        'subject': 'categories',
2247        'currency': 'amount',
2248        'amount_per_grant_max': 'amount',
2249        'amount_per_grant_min': 'amount',
2250        'status': 'status',
2251        'earliest_submission': 'deadline',
2252        'relative_deadline': 'deadline',
2253        'type': 'sponsorType',
2254        'grant_types': 'grantType',
2255        'applicant_locations': 'applicantLocations',
2256        'activity_locations': 'activityLocations',
2257        'is_limited': 'submission',
2258        'applicant_type_ids': 'applicantType',
2259        'cfda_numbers': 'cfdaNum',
2260        'citizenship': 'citizenship',
2261        'sponsor_country': 'sponsorCountry'
2262
2263    };
2264
2265    //reverse map to check for filters.
2266    var reverseMap = {};
2267    for (var item in map) {
2268        if (!map.hasOwnProperty(item)) {
2269            continue;
2270        }
2271        if (typeof (reverseMap[map[item]]) == 'undefined') {
2272            reverseMap[map[item]] = []
2273        }
2274        reverseMap[map[item]].push(item);
2275    }
2276
2277    /**
2278     * check and open any filter if it has any conditions up
2279     * @param  {string} name a specific filter name, every filters if name is null
2280     * @return {None}
2281     */
2282    function checkOpenFilter(name) {
2283        if (!name) {
2284            for (var item in reverseMap) {
2285                if (!reverseMap.hasOwnProperty(item)) {
2286                    continue;
2287                }
2288                _checkOpenFilter(item);
2289            }
2290        } else {
2291            _checkOpenFilter(name);
2292        }
2293    }
2294
2295    /**
2296     * function that handle check of each single item
2297     * @param  {string} name name of the filter, cannot be none anymore
2298     * @return {None}
2299     */
2300    function _checkOpenFilter(name) {
2301        if (_checkIfActive(name) === true) {
2302            if (elm(name).length > 0) {
2303                elm(name).collapse('show');
2304            }
2305        }
2306    }
2307
2308    function _checkIfActive(name) {
2309        //check all the items of the reverseMap item
2310        //if it appear in query object then open it
2311        if (typeof (reverseMap[name]) == 'undefined') {
2312            return;
2313        }
2314        var checkList = reverseMap[name];
2315        var open = false;
2316        for (var i = 0; i < checkList.length; i++) {
2317            if (typeof (me.query.advanced[checkList[i]]) != 'undefined' && !$.isEmptyObject(me.query.advanced[checkList[i]])) {
2318                open = true;
2319                break;
2320            }
2321        }
2322        return open;
2323    }
2324
2325    /** reset the panel effect */
2326    function checkActiveFilter(name) {
2327        if (!name) {
2328            for (var item in reverseMap) {
2329                if (!reverseMap.hasOwnProperty(item)) {
2330                    continue;
2331                }
2332                _checkFilter(item);
2333            }
2334        } else {
2335            _checkFilter(name);
2336        }
2337    }
2338
2339    function _checkFilter(name) {
2340        if (elm(name).length === 0) {
2341            return;
2342        }
2343        if (_checkIfActive(name) === true) {
2344            elm(name).prev().addClass('search-panel-active');
2345        } else {
2346            elm(name).prev().removeClass('search-panel-active');
2347        }
2348    }
2349
2350    /**
2351     * bind event handlers to their according triggers
2352     * @return {None}
2353     */
2354    function bindEvents() {
2355        //when shown, change the color of panel title to default + icon
2356        elm('filter').on('shown.bs.collapse', function (e) {
2357            if (e.target.id == elements["excludeLocation"] || e.target.id == elements["excludeSponsor"] || e.target.id == elements["excludeActivityLocation"] || e.target.id == elements["excludeCitizenship"]) {
2358                return;
2359            }
2360            var panelTitleElm = $(this).prev(); /*previous item = heading*/
2361            panelTitleElm
2362                .find(elements['filter.open']).addClass('hidden')
2363                .end() /* reset chain */
2364                .find(elements['filter.close']).removeClass('hidden')
2365                .end()
2366                .find(elements['filter.toggle']).attr('aria-expanded', 'true');
2367        });
2368        //when hidden, change the color of panel title to active if there is something there + icon
2369        elm('filter').on('hidden.bs.collapse', function (e) {
2370            if (e.target.id == elements["excludeLocation"] || e.target.id == elements["excludeSponsor"] || e.target.id == elements["excludeActivityLocation"] || e.target.id == elements["excludeCitizenship"]) {
2371                return;
2372            }
2373            var panelTitleElm = $(this).prev(); /*previous item = heading*/
2374            panelTitleElm
2375                .find(elements['filter.close']).addClass('hidden')
2376                .end() /* reset chain */
2377                .find(elements['filter.open']).removeClass('hidden')
2378                .end()
2379                .find(elements['filter.toggle']).attr('aria-expanded', 'false');
2380        });
2381        //if search reset is clicked ==> also restore the state of panel titles
2382        //to cover all the cases
2383        $(document).on('czd_search_filter_before_change', function (e) {
2384            checkActiveFilter();
2385        });
2386
2387        elm('filter.toggle').on('click', function () {
2388        });
2389
2390        elm('filter.toggle').on('keydown', function (event) {
2391            if (event.which === 32) {
2392                event.preventDefault();
2393                $(event.currentTarget).click();
2394            }
2395        });
2396    }
2397
2398    /**
2399     * initialize function
2400     * @return {None}
2401     */
2402    function init() {
2403        bindEvents();
2404    }
2405
2406    //run initialization function
2407    init();
2408
2409    //expose any method / attributes here
2410    return {
2411        checkOpenFilter: checkOpenFilter
2412    };
2413
2414};
2415
2416
2417//all search filter tooltips should be initialized
2418$(document).ready(function () {
2419
2420    //2.9: the goto filter
2421    $('.gotofilter').on('change', function (e) {
2422        var pivot = $(this).val();
2423        var topVal;
2424        var element = null;
2425        if (pivot === '') {
2426            return;
2427        } else {
2428            element = $('[js-pivot="' + pivot + '"]');
2429            if (element.length === 0) {
2430                return;
2431            } else {
2432                topVal = element.offset().top;
2433            }
2434        }
2435        _trackGSEvent(
2436            '2.0-Index',
2437            'filter_shortcut',
2438            pivot
2439        );
2440
2441        $('html, body').animate({
2442            'scrollTop': Math.max(0, topVal - 70) //the buffer
2443        }, 800, function () {
2444            $('.gotofilter').val('');
2445            if (element !== null) {
2446                element.find('.accordion-toggle').focus();
2447                element.find(".panel-collapse").collapse('show');
2448            }
2449        });
2450    });
2451
2452    $('.back-top-from-filter a').on('click', function () {
2453        $('html, body').animate({
2454            'scrollTop': 0
2455        }, 800, function () {
2456            $('.skip-nav.skip-main').focus();
2457        });
2458    });
2459
2460    $('.search-filter-tooltip').each(function () {
2461        $(this).attr('aria-label', $(this).attr('title'));
2462    });
2463    $('.search-filter-tooltip').tooltip({
2464        container: 'body'
2465    });
2466
2467    //init the share url popup to spare the trouble of handling load callback
2468    $('.share-search-popup').modal({
2469        remote: "/search/get_shared_search_template",
2470        show: false,
2471    });
2472    $(document).on('mouseenter touchstart focus', 'button.comment_button', function () {
2473        if (!$(this).controller('CommentButton')) {
2474            //init comments
2475            $(this).comment_button();
2476        }
2477    });
2478
2479    // Duy Dinh Init Accessilibity to tag list
2480    if (window.czdAccess) {
2481        window.czdAccess.addAccessibilityToButtonGroup($(".tag-container"))
2482        window.czdAccess.addAccessibilityToDropDown(".czd-dropdown", "GrantSaveLoadDropDown")
2483    }
2484});

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.