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.