1// custom select module 2jcf.addModule({ 3 name:'select', 4 selector:'select', 5 defaultOptions: { 6 useNativeDropOnMobileDevices: true, 7 hideDropOnScroll: true, 8 showNativeDrop: false, 9 handleDropPosition: true, 10 selectDropPosition: 'bottom', // or 'top' 11 wrapperClass:'select-area', 12 focusClass:'select-focus', 13 dropActiveClass:'select-active', 14 selectedClass:'item-selected', 15 currentSelectedClass:'current-selected', 16 disabledClass:'select-disabled', 17 valueSelector:'span.center', 18 optGroupClass:'optgroup', 19 openerSelector:'a.select-opener', 20 selectStructure:'<span class="left"></span><span class="center"></span><a class="select-opener"></a>', 21 wrapperTag: 'span', 22 classPrefix:'select-', 23 dropMaxHeight: 200, 24 dropFlippedClass: 'select-options-flipped', 25 dropHiddenClass:'options-hidden', 26 dropScrollableClass:'options-overflow', 27 dropClass:'select-options', 28 dropClassPrefix:'drop-', 29 dropStructure:'<div class="drop-holder"><div class="drop-list"></div></div>', 30 dropSelector:'div.drop-list' 31 }, 32 checkElement: function(el){ 33 if ($(el).hasClass('js-select-styler-filter') || $(el).hasClass('js-select-styler')) return false; 34 return (!el.size && !el.multiple); 35 }, 36 setupWrapper: function(){ 37 jcf.lib.addClass(this.fakeElement, this.options.wrapperClass); 38 this.realElement.parentNode.insertBefore(this.fakeElement, this.realElement); 39 this.fakeElement.innerHTML = this.options.selectStructure; 40 this.fakeElement.style.width = (this.realElement.offsetWidth > 0 ? this.realElement.offsetWidth + 'px' : 'auto'); 41 42 // show native drop if specified in options 43 // if(this.options.useNativeDropOnMobileDevices && (jcf.isTouchDevice || jcf.isWinPhoneDevice)) { 44 // this.options.showNativeDrop = true; 45 // } 46 if(this.options.showNativeDrop) { 47 this.fakeElement.appendChild(this.realElement); 48 jcf.lib.removeClass(this.realElement, this.options.hiddenClass); 49 jcf.lib.setStyles(this.realElement, { 50 top:0, 51 left:0, 52 margin:0, 53 padding:0, 54 opacity:0, 55 border:'none', 56 position:'absolute', 57 width: jcf.lib.getInnerWidth(this.fakeElement) - 1, 58 height: jcf.lib.getInnerHeight(this.fakeElement) - 1 59 }); 60 jcf.lib.event.add(this.realElement, jcf.eventPress, function(){ 61 this.realElement.title = ''; 62 }, this) 63 } 64 65 // create select body 66 this.opener = jcf.lib.queryBySelector(this.options.openerSelector, this.fakeElement)[0]; 67 this.valueText = jcf.lib.queryBySelector(this.options.valueSelector, this.fakeElement)[0]; 68 jcf.lib.disableTextSelection(this.valueText); 69 this.opener.jcf = this; 70 71 if(!this.options.showNativeDrop) { 72 this.createDropdown(); 73 this.refreshState(); 74 this.onControlReady(this); 75 this.hideDropdown(true); 76 } else { 77 this.refreshState(); 78 } 79 this.addEvents(); 80 }, 81 addEvents: function(){ 82 if(this.options.showNativeDrop) { 83 jcf.lib.event.add(this.realElement, 'click', this.onChange, this); 84 } else { 85 jcf.lib.event.add(this.fakeElement, 'click', this.toggleDropdown, this); 86 } 87 jcf.lib.event.add(this.realElement, 'change', this.onChange, this); 88 }, 89 onFakeClick: function() { 90 // do nothing (drop toggles by toggleDropdown method) 91 }, 92 onFocus: function(){ 93 jcf.modules[this.name].superclass.onFocus.apply(this, arguments); 94 if(!this.options.showNativeDrop) { 95 // Mac Safari Fix 96 if(jcf.lib.browser.safariMac) { 97 this.realElement.setAttribute('size','2'); 98 } 99 jcf.lib.event.add(this.realElement, 'keydown', this.onKeyDown, this); 100 if(jcf.activeControl && jcf.activeControl != this) { 101 jcf.activeControl.hideDropdown(); 102 jcf.activeControl = this; 103 } 104 } 105 }, 106 onBlur: function(){ 107 if(!this.options.showNativeDrop) { 108 // Mac Safari Fix 109 if(jcf.lib.browser.safariMac) { 110 this.realElement.removeAttribute('size'); 111 } 112 if(!this.isActiveDrop() || !this.isOverDrop()) { 113 jcf.modules[this.name].superclass.onBlur.apply(this); 114 if(jcf.activeControl === this) jcf.activeControl = null; 115 if(!jcf.isTouchDevice) { 116 this.hideDropdown(); 117 } 118 } 119 jcf.lib.event.remove(this.realElement, 'keydown', this.onKeyDown); 120 } else { 121 jcf.modules[this.name].superclass.onBlur.apply(this); 122 } 123 }, 124 onChange: function() { 125 this.refreshState(); 126 }, 127 onKeyDown: function(e){ 128 this.dropOpened = true; 129 jcf.tmpFlag = true; 130 setTimeout(function(){jcf.tmpFlag = false},100); 131 var context = this; 132 context.keyboardFix = true; 133 setTimeout(function(){ 134 context.refreshState(); 135 },10); 136 if(e.keyCode == 13) { 137 context.toggleDropdown.apply(context); 138 return false;
139 } 140 }, 141 onResizeWindow: function(e){ 142 if(this.isActiveDrop()) { 143 this.hideDropdown(); 144 } 145 }, 146 onScrollWindow: function(e){ 147 if(this.options.hideDropOnScroll) { 148 this.hideDropdown(); 149 } else if(this.isActiveDrop()) { 150 this.positionDropdown(); 151 } 152 }, 153 onOptionClick: function(e){ 154 var opener = e.target && e.target.tagName && e.target.tagName.toLowerCase() == 'li' ? e.target : jcf.lib.getParent(e.target, 'li'); 155 if(opener) { 156 this.dropOpened = true; 157 this.realElement.selectedIndex = parseInt(opener.getAttribute('rel')); 158 if(jcf.isTouchDevice) { 159 this.onFocus(); 160 } else { 161 this.realElement.focus(); 162 } 163 this.refreshState(); 164 this.hideDropdown(); 165 jcf.lib.fireEvent(this.realElement, 'change'); 166 } 167 return false; 168 }, 169 onClickOutside: function(e){ 170 if(jcf.tmpFlag) { 171 jcf.tmpFlag = false; 172 return; 173 } 174 if(!jcf.lib.isParent(this.fakeElement, e.target) && !jcf.lib.isParent(this.selectDrop, e.target)) { 175 this.hideDropdown(); 176 } 177 }, 178 onDropHover: function(e){ 179 if(!this.keyboardFix) { 180 this.hoverFlag = true; 181 var opener = e.target && e.target.tagName && e.target.tagName.toLowerCase() == 'li' ? e.target : jcf.lib.getParent(e.target, 'li'); 182 if(opener) { 183 this.realElement.selectedIndex = parseInt(opener.getAttribute('rel')); 184 this.refreshSelectedClass(parseInt(opener.getAttribute('rel'))); 185 } 186 } else { 187 this.keyboardFix = false; 188 } 189 }, 190 onDropLeave: function(){ 191 this.hoverFlag = false; 192 }, 193 isActiveDrop: function(){ 194 return !jcf.lib.hasClass(this.selectDrop, this.options.dropHiddenClass); 195 }, 196 isOverDrop: function(){ 197 return this.hoverFlag; 198 }, 199 createDropdown: function(){ 200 // remove old dropdown if exists 201 if(this.selectDrop) { 202 this.selectDrop.parentNode.removeChild(this.selectDrop); 203 } 204 205 // create dropdown holder 206 this.selectDrop = document.createElement('div'); 207 this.selectDrop.className = this.options.dropClass; 208 this.selectDrop.innerHTML = this.options.dropStructure; 209 jcf.lib.setStyles(this.selectDrop, {position:'absolute'}); 210 this.selectList = jcf.lib.queryBySelector(this.options.dropSelector,this.selectDrop)[0]; 211 jcf.lib.addClass(this.selectDrop, this.options.dropHiddenClass); 212 document.body.appendChild(this.selectDrop); 213 this.selectDrop.jcf = this; 214 jcf.lib.event.add(this.selectDrop, 'click', this.onOptionClick, this); 215 jcf.lib.event.add(this.selectDrop, 'mouseover', this.onDropHover, this); 216 jcf.lib.event.add(this.selectDrop, 'mouseout', this.onDropLeave, this); 217 this.buildDropdown(); 218 }, 219 buildDropdown: function() { 220 // build select options / optgroups 221 this.buildDropdownOptions(); 222 223 // position and resize dropdown 224 this.positionDropdown(); 225 226 // cut dropdown if height exceedes 227 this.buildDropdownScroll(); 228 }, 229 buildDropdownOptions: function() { 230 this.resStructure = ''; 231 this.optNum = 0; 232 for(var i = 0; i < this.realElement.children.length; i++) { 233 this.resStructure += this.buildElement(this.realElement.children[i], i) +'\n'; 234 } 235 this.selectList.innerHTML = this.resStructure; 236 }, 237 buildDropdownScroll: function() { 238 jcf.lib.addClass(this.selectDrop, jcf.lib.getAllClasses(this.realElement.className, this.options.dropClassPrefix, jcf.baseOptions.hiddenClass)); 239 if(this.options.dropMaxHeight) { 240 if(this.selectDrop.offsetHeight > this.options.dropMaxHeight) { 241 this.selectList.style.height = this.options.dropMaxHeight+'px'; 242 this.selectList.style.overflow = 'auto'; 243 this.selectList.style.overflowX = 'hidden'; 244 jcf.lib.addClass(this.selectDrop, this.options.dropScrollableClass); 245 } 246 } 247 }, 248 parseOptionTitle: function(optTitle) { 249 return (typeof optTitle === 'string' && /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i.test(optTitle)) ? optTitle : ''; 250 }, 251 buildElement: function(obj, index){ 252 // build option 253 var res = '', optImage; 254 if(obj.tagName.toLowerCase() == 'option') { 255 if(!jcf.lib.prevSibling(obj) || jcf.lib.prevSibling(obj).tagName.toLowerCase() != 'option') { 256 res += '<ul>'; 257 } 258 259 optImage = this.parseOptionTitle(obj.title); 260 res += '<li rel="'+(this.optNum++)+'" class="'+(obj.className? obj.className + ' ' : '')+(index % 2 ? 'option-even ' : '')+'jcfcalc"><a href="#">'+(optImage ? '<img src="'+optImage+'" alt="" />
260' : '')+'<span>' + obj.innerHTML + '</span></a></li>'; 261 if(!jcf.lib.nextSibling(obj) || jcf.lib.nextSibling(obj).tagName.toLowerCase() != 'option') { 262 res += '</ul>'; 263 } 264 return res; 265 } 266 // build option group with options 267 else if(obj.tagName.toLowerCase() == 'optgroup' && obj.label) { 268 res += '<div class="'+this.options.optGroupClass+'">'; 269 res += '<strong class="jcfcalc"><em>'+(obj.label)+'</em></strong>'; 270 for(var i = 0; i < obj.children.length; i++) { 271 res += this.buildElement(obj.children[i], i); 272 } 273 res += '</div>'; 274 return res; 275 } 276 }, 277 positionDropdown: function(){ 278 var ofs = jcf.lib.getOffset(this.fakeElement), selectAreaHeight = this.fakeElement.offsetHeight, selectDropHeight = this.selectDrop.offsetHeight; 279 var fitInTop = ofs.top - selectDropHeight >= jcf.lib.getScrollTop() && jcf.lib.getScrollTop() + jcf.lib.getWindowHeight() < ofs.top + selectAreaHeight + selectDropHeight; 280 281 282 if((this.options.handleDropPosition && fitInTop) || this.options.selectDropPosition === 'top') { 283 this.selectDrop.style.top = (ofs.top - selectDropHeight)+'px'; 284 jcf.lib.addClass(this.fakeElement, this.options.dropFlippedClass); 285 jcf.lib.addClass(this.selectDrop, this.options.dropFlippedClass); 286 } else { 287 this.selectDrop.style.top = (ofs.top + selectAreaHeight)+'px'; 288 jcf.lib.removeClass(this.fakeElement, this.options.dropFlippedClass); 289 jcf.lib.removeClass(this.selectDrop, this.options.dropFlippedClass); 290 } 291 this.selectDrop.style.left = ofs.left+'px'; 292 this.selectDrop.style.width = this.fakeElement.offsetWidth+'px'; 293 }, 294 showDropdown: function(){ 295 document.body.appendChild(this.selectDrop); 296 jcf.lib.removeClass(this.selectDrop, this.options.dropHiddenClass); 297 jcf.lib.addClass(this.fakeElement,this.options.dropActiveClass); 298 this.positionDropdown(); 299 300 // highlight current active item 301 var activeItem = this.getFakeActiveOption(); 302 this.removeClassFromItems(this.options.currentSelectedClass); 303 jcf.lib.addClass(activeItem, this.options.currentSelectedClass); 304 305 // show current dropdown 306 jcf.lib.event.add(window, 'resize', this.onResizeWindow, this); 307 jcf.lib.event.add(window, 'scroll', this.onScrollWindow, this); 308 jcf.lib.event.add(document, jcf.eventPress, this.onClickOutside, this); 309 this.positionDropdown(); 310 }, 311 hideDropdown: function(partial){ 312 if(this.selectDrop.parentNode) { 313 if(this.selectDrop.offsetWidth) { 314 this.selectDrop.parentNode.removeChild(this.selectDrop); 315 } 316 if(partial) { 317 return; 318 } 319 } 320 if(typeof this.origSelectedIndex === 'number') { 321 this.realElement.selectedIndex = this.origSelectedIndex; 322 } 323 jcf.lib.removeClass(this.fakeElement,this.options.dropActiveClass); 324 jcf.lib.addClass(this.selectDrop, this.options.dropHiddenClass); 325 jcf.lib.event.remove(window, 'resize', this.onResizeWindow); 326 jcf.lib.event.remove(window, 'scroll', this.onScrollWindow); 327 jcf.lib.event.remove(document.documentElement, jcf.eventPress, this.onClickOutside); 328 if(jcf.isTouchDevice) { 329 this.onBlur(); 330 } 331 }, 332 toggleDropdown: function(){ 333 if(!this.realElement.disabled) { 334 if(jcf.isTouchDevice) { 335 this.onFocus(); 336 } else { 337 this.realElement.focus(); 338 } 339 if(this.isActiveDrop()) { 340 this.hideDropdown(); 341 } else { 342 this.showDropdown(); 343 } 344 this.refreshState(); 345 } 346 }, 347 scrollToItem: function(){ 348 if(this.isActiveDrop()) { 349 var dropHeight = this.selectList.offsetHeight; 350 var offsetTop = this.calcOptionOffset(this.getFakeActiveOption()); 351 var sTop = this.selectList.scrollTop; 352 var oHeight = this.getFakeActiveOption().offsetHeight; 353 //offsetTop+=sTop; 354 355 if(offsetTop >= sTop + dropHeight) { 356 this.selectList.scrollTop = offsetTop - dropHeight + oHeight; 357 } else if(offsetTop < sTop) { 358 this.selectList.scrollTop = offsetTop; 359 } 360 } 361 }, 362 getFakeActiveOption: function(c) { 363 return jcf.lib.queryBySelector('li[rel="'+(typeof c === 'number' ? c : this.realElement.selectedIndex) +'"]',this.selectList)[0]; 364 }, 365 calcOptionOffset: function(fake) { 366 var h = 0;
367 var els = jcf.lib.queryBySelector('.jcfcalc',this.selectList); 368 for(var i = 0; i < els.length; i++) { 369 if(els[i] == fake) break; 370 h+=els[i].offsetHeight; 371 } 372 return h; 373 }, 374 childrenHasItem: function(hold,item) { 375 var items = hold.getElementsByTagName('*'); 376 for(i = 0; i < items.length; i++) { 377 if(items[i] == item) return true; 378 } 379 return false; 380 }, 381 removeClassFromItems: function(className){ 382 var children = jcf.lib.queryBySelector('li',this.selectList); 383 for(var i = children.length - 1; i >= 0; i--) { 384 jcf.lib.removeClass(children[i], className); 385 } 386 }, 387 setSelectedClass: function(c){ 388 jcf.lib.addClass(this.getFakeActiveOption(c), this.options.selectedClass); 389 }, 390 refreshSelectedClass: function(c){ 391 if(!this.options.showNativeDrop) { 392 this.removeClassFromItems(this.options.selectedClass); 393 this.setSelectedClass(c); 394 } 395 if(this.realElement.disabled) { 396 jcf.lib.addClass(this.fakeElement, this.options.disabledClass); 397 if(this.labelFor) { 398 jcf.lib.addClass(this.labelFor, this.options.labelDisabledClass); 399 } 400 } else { 401 jcf.lib.removeClass(this.fakeElement, this.options.disabledClass); 402 if(this.labelFor) { 403 jcf.lib.removeClass(this.labelFor, this.options.labelDisabledClass); 404 } 405 } 406 }, 407 refreshSelectedText: function() { 408 if(!this.dropOpened && this.realElement.title) { 409 this.valueText.innerHTML = this.realElement.title; 410 } else { 411 if(this.realElement.options[this.realElement.selectedIndex].title) { 412 var optImage = this.parseOptionTitle(this.realElement.options[this.realElement.selectedIndex].title); 413 this.valueText.innerHTML = (optImage ? '<img src="'+optImage+'" alt="" />' : '') + this.realElement.options[this.realElement.selectedIndex].innerHTML; 414 } else { 415 this.valueText.innerHTML = this.realElement.options[this.realElement.selectedIndex].innerHTML; 416 } 417 } 418 }, 419 refreshState: function(){ 420 this.origSelectedIndex = this.realElement.selectedIndex; 421 this.refreshSelectedClass(); 422 this.refreshSelectedText(); 423 if(!this.options.showNativeDrop) { 424 this.positionDropdown(); 425 if(this.selectDrop.offsetWidth) { 426 this.scrollToItem(); 427 } 428 } 429 } 430});
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.