PageSourceSearch

https://members.marinbuilders.com/Content/Script/SearchWidget.js

js marinbuilders.com collected 2026-09-29 16:06:21 UTC 18,719 bytes, 508 lines download raw bytes

1var MNI = MNI || {};
2
3(function (document, window, MNI) {
4    
5    var IsCms = true;
6    var NULL = null,
7        prefix = 'mn-widget-searchbox',
8        widgets = MNI.Widgets || (MNI.Widgets = {}),
9        callbacks = MNI.Callbacks || (MNI.Callbacks = {});
10
11    function extend(target, source) {
12        var name, value;
13        
14        target = target || {};
15
16        for(name in source) {
17            if ((value = source[name]) !== undefined)
18                target[name] = value;
19        }
20
21        return target;
22    };
23
24    function addClass(element, className) {
25        element.className += ' ' + className;
26    };
27
28    function removeClass(element, className) {
29        element.className = element.className.replace(new RegExp('(?:^|\\s)' + className + '(?!\\S)'), '');
30    };
31
32    /*
33    * @param {string} type
34    * @param {Object=} attributes
35    * @param {string=} className
36    */
37    function createElement(type, attributes, className) {
38        var element = document.createElement(type);
39        for (var name in attributes)
40            element.setAttribute(name, attributes[name]);
41        if (className)
42            addClass(element, className);
43        return element;
44    };
45
46    /*
47    * @param {string} text
48    * @param {string=} className
49    */
50    function createSpanElement(text, className) {
51        var element = createElement('span', NULL, className);
52        appendTextNode(element, text);
53        return element;
54    };
55
56    /*
57    * @param {!Element} element
58    * @param {?Object} text
59    */
60    function appendTextNode(element, text) {
61        if (text == null)
62            text = '';
63
64        element.appendChild(document.createTextNode(String(text)));
65        return element;
66    };
67
68    function addEventListener(element, name, handler)
69    {
70        if (element['addEventListener'])
71            element.addEventListener(name, handler);
72        else
73            element.attachEvent('on' + name, handler);
74    };
75
76    function preventDefault(e) {
77        if (e = e || window.event)
78            e.preventDefault && e.preventDefault() || (e.returnValue = false);
79    };
80
81    function stopPropagation(e) {
82        if (e = e || window.event)
83            e.stopPropagation && e.stopPropagation() || (e.cancelBubble = true);
84    };
85
86    function getScriptTagData() {
87        var scripts = document.getElementsByTagName('script');
88
89        for (var i = 0; i < scripts.length; i++) {
90            var script = scripts[i],
91                match = script.src.match(/^(https?:)?\/\/([^/]+)\/(public\/)?content\/script\/searchwidget\.(min\.)?js/i);
92
93            if (match) {
94                return {
95                    protocol: match[1] || window.location.protocol,
96                    domain: match[2]
97                };
98            }
99        }
100
101        return {};
102    };
103
104    /**
105    * @param {string} url
106    * @param {Object} data
107    * @return {string}
108    */
109    function urlWithParams(url, data) {
110        /**
111        * @param {Array.<string>} array
112        * @param {string} name
113        * @param {Object} value
114        */
115        function addEntry(array, name, value) {
116            array.push(window['encodeURIComponent'](name) + '=' + window['encodeURIComponent'](value));
117        };
118
119        var parts = [];
120
121        url = url || '';
122
123        for (name in data) {
124            if ((value = data[name]) != undefined) {
125                if (typeof value === 'object' && 'length' in value) {
126                    for (var i = 0; i < value.length; i++)
127                        addEntry(parts, name, value[i]);
128                }
129                else
130                    addEntry(parts, name, value);
131            }
132        }
133
134        if (parts.length)
135            url += (url.indexOf('?') >= 0 ? '&' : '?') + parts.join('&');
136
137        return url;
138    };
139
140    function AjaxRequest(url, data, callback) {
141        var firstScript = document.getElementsByTagName('script')[0],
142            script = createElement('script', NULL, NULL),
143            callbackName = 'Ajax' + +new Date,
144            queryString = [],
145            name,
146            value;
147
148        data = extend({
149            jsonpcallback: 'MNI.Callbacks.' + callbackName
150        }, data);
151
152        callbacks[callbackName] = function(data) {
153            scriptOnLoad.call(script);
154            callback(data);
155        };
156
157        script.async = true;
158        script.onload = script.onreadystatechange = scriptOnLoad;
159        script.src = urlWithParams(url, data);
160        firstScript.parentNode.insertBefore(script, firstScript);
161    };
162
163    function proxy(fn, context) {
164        return function () {
165            return fn.apply(context, arguments);
166        };
167    };
168
169    function Autocomplete(element, list, ajaxUrl, domain, limit, opts) {
170        opts = extend({}, opts);
171
172        var self = this;
173
174        self._element = element;
175        self._list = list;
176        self._ajaxUrl = ajaxUrl;
177        self._domain = domain;
178        self._limit = limit;
179        self._cache = [];
180        self._show = false;
181        self._onChange = opts.onChange;
182        self._extraData = opts.extraData;
183
184        var showProxy = proxy(self.show, self);
185        var hideProxy = proxy(self.hide, self);
186
187        self._selectProxy = proxy(self.select, self);
188        self._changeProxy = proxy(self.change, self);
189
190        addEventListener(element, 'keyup', showProxy);
191        addEventListener(element, 'focus', showProxy);
192        addEventListener(element, 'blur', hideProxy);
193
194        return self;
195    };
196
197    extend(Autocomplete.prototype, {
198        'update': function () {
199            var self = this,
200                keyword = self._element.value,
201                parameters = {
202                    q: keyword,
203                    limit: self._limit
204                },
205                cachedData = self._cache[keyword];
206
207            if (cachedData !== undefined) {
208                if (cachedData)
209                    self.render(cachedData);
210            } else if (keyword && keyword.length > 1) {
211                self._cache[keyword] = NULL;
212                extend(parameters, self._extraData);
213                new AjaxRequest(self._ajaxUrl, parameters, function (data) {
214                    self._cache[keyword] = data;
215
216                    if (keyword === self._element.value)
217                        self.render(data);
218                });
219            } else {
220                self.render([]);
221            }
222        },
223        show: function () {
224            var self = this;
225
226            self._show = true;
227            self.update();
228        },
229        hide: function () {
230            var self = this;
231            self._show = false;
232
233            setTimeout(function () {
234                self._list.style.display = 'none';
235            }, 500);
236        },
237        select: function (event) {
238            var self = this,
239                target = event.currentTarget,
240                record = target.autocompleteRecord;
241
242            self._element.value = record['Name'];
243
244            if (self._onChange && self._onChange(self, record, target) === false) {
245                preventDefault(event);
246                stopPropagation(event);
247            }
248        },
249        render: function (data) {
250            var self = this,
251                list = self._list;
252
253            while (list.firstChild)
254                list.removeChild(list.firstChild);
255
256            for (var i = 0; i < data.length; i++) {
257                var record = data[i],
258                    searchResult = createElement('li'),
259                    urlMap = typeUrlMap[record['ResultType']],
260                    nameMap = typeNameMap[record['ResultType']],
261                    nameElement = createElement('span', NULL, 'mn-autocomplete-name'),
262                    typeElement = createElement('span', NULL, 'mn-autocomplete-type');
263
264                var linkElement = urlMap ?
265                    createElement('a', { href: (IsCms ? getScriptTagData().protocol : 'http:') + '//' + self._domain + urlMap(record) }, 'mn-autocomplete') :
266                    createSpanElement(NULL, 'mn-autocomplete');
267
268                linkElement.autocompleteRecord = record;
269                addEventListener(linkElement, 'click', self._selectProxy);
270
271                appendTextNode(nameElement, record['Name']);
272                appendTextNode(typeElement, nameMap(record));
273
274                linkElement.appendChild(nameElement);
275                linkElement.appendChild(createElement('br'));
276                linkElement.appendChild(typeElement);
277
278                searchResult.appendChild(linkElement);
279
280                list.appendChild(searchResult);
281            }
282
283            list.style.display = (list.firstChild && self._show) ? 'block' : 'none';
284        }
285    });
286
287    function scriptOnLoad() {
288        if (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete') {
289            this.onload = this.onreadystatechange = NULL;
290            this.parentNode.removeChild(this);
291        }
292    };
293
294    function SearchBox(element, opts) {
295        if (typeof element === 'string')
296            element = document.getElementById(element);
297
298        opts = extend({
299            buttonText: 'Search',
300            distanceUnit: 'mi',
301            keywordPlaceholder: '',
302            regionPlaceholder: 'Enter your city or postal code',
303            radiusOptions: [15, 25, 50, 100, 200, 500],
304            defaultRadius: 25
305        }, opts);
306
307        this._element = element;
308        this._domain = opts.domain;
309        this._secure = opts.secure;
310        this._limit = opts.limit;
311        this._location = opts.location;
312        this._buttonText = opts.buttonText;
313        this._styleTemplate = opts.styleTemplate;
314        this._locationType = opts.locationType;
315        this._distanceUnit = opts.distanceUnit;
316        this._keywordPlaceholder = opts.keywordPlaceholder;
317        this._regionPlaceholder = opts.regionPlaceholder;
318        this._radiusOptions = opts.radiusOptions;
319        this._defaultRadius = opts.defaultRadius;
320    };
321
322    var typeNameMap = {
323        // AutocompleteSearchResultType.Category
324        1: function (data) { return 'Category' },
325
326        // AutocompleteSearchResultType.QuickLink
327        2: function (data) { return 'Quicklink' },
328
329        // AutocompleteSearchResultType.Member
330        3: function (data) { return 'Type' in data ? (data.Type || '') : 'Member' }
331    },
332        typeUrlMap = {
333            // AutocompleteSearchResultType.Category
334            1: function(data) { return '/list/category/' + data['SlugWithID'] },
335
336            // AutocompleteSearchResultType.QuickLink
337            2: function(data) { return '/list/ql/' + data['SlugWithID'] },
338
339            // AutocompleteSearchResultType.Member
340            3: function(data) {
341                if (data['WebParticipation'] < 10) // WebParticipation.Basic
342                    return '/list/search?q=' + window.encodeURIComponent(data['Name']); 
343
344                return '/list/member/' + data['SlugWithID'];
345            }
346        };
347
348    extend(SearchBox.prototype, {
349        _cache: {},
350        'create': function() {
351            if (this._list)
352                return;
353
354            var self = this,
355                form = self._form = createElement('form', { method: 'get', action: self._getGenericSearchUrl() }, prefix + '-form'),
356                textboxWrapper = createElement('span', NULL, prefix + '-wrapper ' + prefix + '-fieldwrapper'),
357                textboxField = createElement('span', NULL, prefix + '-field'),
358                textbox = self._textbox = createElement('input', { type: 'text', name: 'q', autocomplete: 'off', placeholder: self._keywordPlaceholder }, prefix + '-keyword'),
359                button = createElement('input', { type: 'submit', value: self._buttonText }, prefix + '-searchbtn'),
360                autocomplete = createElement('div', NULL, prefix + '-autocomplete'),
361                list = createElement('ul', { style: 'display:none' }),
362                styleTemplate = self._styleTemplate;
363
364            if (styleTemplate) {
365                var containerId = self._element.getAttribute('id');
366
367                if (!containerId) {
368                    containerId = 'mni-search-' + +new Date;
369                    self._element.setAttribute('id', containerId);
370                }
371
372                var headElement = document.getElementsByTagName('head')[0],
373                    styleElement,
374                    tokens = /@\w+/g,
375                    patterns = {
376                        id: containerId
377                    },
378                    css = styleTemplate.replace(tokens, function(segment) {
379                        return patterns[segment.slice(1)] || segment;
380                    });
381
382                if (document.createStyleSheet) {
383                    styleElement = document.createStyleSheet();
384                    styleElement.cssText = css;
385                } else {
386                    styleElement = createElement('style', { id: containerId + '-style' }, NULL);
387                    styleElement.appendChild(document.createTextNode(css));
388
389                    if (headElement)
390                        headElement.appendChild(styleElement);
391                }
392            }
393
394            addEventListener(textbox, 'change', function () {
395                self._setSearchMode();
396            });
397
398            new Autocomplete(textbox, list, self._getAjaxURL(), self._getDomain(), self._limit, {
399                extraData: {
400                    fl: self._location
401                },
402                onChange: function (autoComplete, record, element) {
403                    if (self._locationType == 4 && record['ResultType'] != 3) {
404                        self._setSearchMode(element.getAttribute('href'));
405                        return false;
406                    }
407                }
408            });
409
410            autocomplete.appendChild(list);
411
412            textboxWrapper.appendChild(textboxField);
413
414            textboxField.appendChild(textbox);
415            textboxField.appendChild(autocomplete);
416
417            form.appendChild(textboxWrapper);
418
419            if (self._locationType == 4) {
420                var locationContainer = createElement('span', NULL, prefix + '-location'),
421                    radiusWrapper = createElement('span', NULL, prefix + '-radiusWrapper ' + prefix + '-fieldwrapper'),
422                    radiusField = createElement('span', NULL, prefix + '-field'),
423                    radiusElement = createElement('select', { name: 'gr' }, prefix + '-radius'),
424                    regionWrapper = createElement('span', NULL, prefix + '-regionWrapper ' + prefix + '-fieldwrapper'),
425                    regionField = createElement('span', NULL, prefix + '-field'),
426                    autocomplete = createElement('div', NULL, prefix + '-autocomplete'),
427                    list = createElement('ul', { style: 'display:none' }),
428                    regionElement = createElement('input', { type: 'text', name: 'gn', autocomplete: 'off', placeholder: self._regionPlaceholder }, prefix + '-region'),
429                    radiusOptions = self._radiusOptions,
430                    unitConversion = self._distanceUnit == 'mi' ?
431                        function(radius) { return radius; } :
432                        function (radius) { return parseFloat((radius * 0.621371).toFixed(6)); };
433
434                for (var i = 0; i < radiusOptions.length; i++) {
435                    var attributes = { value: unitConversion(radiusOptions[i]) };
436
437                    if (radiusOptions[i] === self._defaultRadius)
438                        attributes['selected'] = 'selected';
439
440                    radiusElement.appendChild(appendTextNode(createElement('option', attributes, NULL), radiusOptions[i]));
441                }
442
443                new Autocomplete(regionElement, list, self._getGeographicAjaxURL(), self._limit);
444
445                autocomplete.appendChild(list);
446
447                radiusWrapper.appendChild(radiusField);
448                radiusField.appendChild(radiusElement);
449
450                regionWrapper.appendChild(regionField);
451                regionField.appendChild(regionElement);
452                regionField.appendChild(autocomplete);
453
454                locationContainer.appendChild(createSpanElement('Within ', prefix + '-preRadius'));
455                locationContainer.appendChild(radiusWrapper);
456                locationContainer.appendChild(createSpanElement(' ' + self._distanceUnit + '. of ', prefix + '-postRadius'));
457                locationContainer.appendChild(regionWrapper);
458
459                form.appendChild(locationContainer);
460            }
461
462            form.appendChild(button);
463
464            this._element.appendChild(form);
465            this._list = list;
466
467            return this;
468        },
469        _setSearchMode: function (url) {
470            var self = this;
471
472            if (url) {
473                self._form.setAttribute('action', url);
474                self._textbox.removeAttribute('name');
475            } else {
476                self._form.setAttribute('action', self._getGenericSearchUrl());
477                self._textbox.setAttribute('name', 'q');
478            }
479        },
480        _getGenericSearchUrl: function () {
481            return (this._getSecure() ? 'https://' : 'http://') + this._getDomain() +'/list/search';
482        },
483        _getDomain: function () {
484            return this._domain || getScriptTagData().domain || window.location.host;
485        },
486        _getAjaxURL: function () {
487            return this._getSecure() ?
488                'https://' + this._getDomain() + (!IsCms ? '/public' : '') + '/list/find' :
489                'http://' + this._getDomain() + '/list/find';
490        },
491        _getGeographicAjaxURL: function () {
492            return this._getSecure() ?
493                'https://' + this._getDomain() + (!IsCms ? '/public' : '') + '/list/find-geographic' :
494                'http://' + this._getDomain() + '/list/find-geographic';
495        },
496        _getSecure: function () {
497            if (this._secure !== undefined)
498                return this._secure;
499
500            return (getScriptTagData().protocol || window.location.protocol) == 'https:' ?
501                true :
502                undefined;
503        }
504    });
505
506    widgets['SearchBox'] = SearchBox;
507
508})(document, window, MNI);

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.