1/*====== 2* 3* Select FX 4* 5======*/ 6/** 7 * selectFx.js v1.0.0 8 * http://www.codrops.com 9 * 10 * Licensed under the MIT license. 11 * http://www.opensource.org/licenses/mit-license.php 12 * 13 * Copyright 2014, Codrops 14 * http://www.codrops.com 15 */ 16;( function( window ) { 17 18 'use strict'; 19 20 /** 21 * based on from https://github.com/inuyaksa/jquery.nicescroll/blob/master/jquery.nicescroll.js 22 */ 23 function hasParent( e, p ) { 24 if (!e) return false; 25 var el = e.target||e.srcElement||e||false; 26 while (el && el != p) { 27 el = el.parentNode||false; 28 } 29 return (el!==false); 30 }; 31 32 /** 33 * extend obj function 34 */ 35 function extend( a, b ) { 36 for( var key in b ) { 37 if( b.hasOwnProperty( key ) ) { 38 a[key] = b[key]; 39 } 40 } 41 return a; 42 } 43 44 /** 45 * SelectFx function 46 */ 47 function SelectFx( el, options ) { 48 this.el = el; 49 this.options = extend( {}, this.options ); 50 extend( this.options, options ); 51 this._init(); 52 } 53 54 /** 55 * SelectFx options 56 */ 57 SelectFx.prototype.options = { 58 // if true all the links will open in a new tab. 59 // if we want to be redirected when we click an option, we need to define a data-link attr on the option of the native select element 60 newTab : true, 61 // when opening the select element, the default placeholder (if any) is shown 62 stickyPlaceholder : true, 63 // callback when changing the value 64 onChange : function( val ) { return false; } 65 } 66 67 /** 68 * init function 69 * initialize and cache some vars 70 */ 71 SelectFx.prototype._init = function() { 72 // check if we are using a placeholder for the native select box 73 // we assume the placeholder is disabled and selected by default 74 var selectedOpt = this.el.querySelector( 'option[selected]' ); 75 this.hasDefaultPlaceholder = selectedOpt && selectedOpt.disabled; 76 77 // get selected option (either the first option with attr selected or just the first option) 78 this.selectedOpt = selectedOpt || this.el.querySelector( 'option' ); 79 80 // create structure 81 this._createSelectEl(); 82 83 // all options 84 this.selOpts = [].slice.call( this.selEl.querySelectorAll( 'li[data-option]' ) ); 85 86 // total options 87 this.selOptsCount = this.selOpts.length; 88 89 // current index 90 this.current = this.selOpts.indexOf( this.selEl.querySelector( 'li.cs-selected' ) ) || -1; 91 92 // placeholder elem 93 this.selPlaceholder = this.selEl.querySelector( 'span.cs-placeholder' ); 94 95 // init events 96 this._initEvents(); 97 } 98 99 /** 100 * creates the structure for the select element 101 */ 102 SelectFx.prototype._createSelectEl = function() { 103 var self = this, options = '', createOptionHTML = function(el) { 104 var optclass = '', classes = '', link = ''; 105 106 if( el.selectedOpt && !this.foundSelected && !this.hasDefaultPlaceholder ) { 107 classes += 'cs-selected '; 108 this.foundSelected = true; 109 } 110 // extra classes 111 if( el.getAttribute( 'data-class' ) ) { 112 classes += el.getAttribute( 'data-class' ); 113 } 114 // link options 115 if( el.getAttribute( 'data-link' ) ) { 116 link = 'data-link=' + el.getAttribute( 'data-link' ); 117 } 118 119 if( classes !== '' ) { 120 optclass = 'class="' + classes + '" '; 121 } 122 123 return '<li ' + optclass + link + ' data-option data-value="' + el.value + '"><span>' + el.textContent + '</span></li>'; 124 }; 125 126 [].slice.call( this.el.children ).forEach( function(el) { 127 if( el.disabled ) { return; } 128 129 var tag = el.tagName.toLowerCase(); 130 131 if( tag === 'option' ) { 132 options += createOptionHTML(el); 133 } 134 else if( tag === 'optgroup' ) { 135 options += '<li class="cs-optgroup"><span>' + el.label + '</span><ul>'; 136 [].slice.call( el.children ).forEach( function(opt) { 137 options += createOptionHTML(opt); 138 } ); 139 options += '</ul></li>'; 140 } 141 } ); 142 143 var opts_el = '<div class="cs-options"><ul>' + options + '</ul></div>'; 144 this.selEl = document.createElement( 'div' ); 145 this.selEl.className = this.el.className; 146 this.selEl.tabIndex = this.el.tabIndex; 147 this.selEl.innerHTML = '<span class="cs-placeholder">' + this.selectedOpt.textContent + '</span>' + opts_el; 148 this.el.parentNode.appendChild( this.selEl ); 149 this.selEl.appendChild( this.el ); 150 } 151 152 /** 153 * initialize the events 154 */ 155 SelectFx.prototype._initEvents = function() { 156 var self = this; 157 158 // open/close select 159 this.selPlaceholder.addEventListener( 'click', function() { 160 self._toggleSelect(); 161 } ); 162 163 // clicking the options
164 this.selOpts.forEach( function(opt, idx) { 165 opt.addEventListener( 'click', function() { 166 self.current = idx; 167 self._changeOption(); 168 // close select elem 169 self._toggleSelect(); 170 } ); 171 } ); 172 173 // close the select element if the target it´s not the select element or one of its descendants.. 174 document.addEventListener( 'click', function(ev) { 175 var target = ev.target; 176 if( self._isOpen() && target !== self.selEl && !hasParent( target, self.selEl ) ) { 177 self._toggleSelect(); 178 } 179 } ); 180 181 // keyboard navigation events 182 this.selEl.addEventListener( 'keydown', function( ev ) { 183 var keyCode = ev.keyCode || ev.which; 184 185 switch (keyCode) { 186 // up key 187 case 38: 188 ev.preventDefault(); 189 self._navigateOpts('prev'); 190 break; 191 // down key 192 case 40: 193 ev.preventDefault(); 194 self._navigateOpts('next'); 195 break; 196 // space key 197 case 32: 198 ev.preventDefault(); 199 if( self._isOpen() && typeof self.preSelCurrent != 'undefined' && self.preSelCurrent !== -1 ) { 200 self._changeOption(); 201 } 202 self._toggleSelect(); 203 break; 204 // enter key 205 case 13: 206 ev.preventDefault(); 207 if( self._isOpen() && typeof self.preSelCurrent != 'undefined' && self.preSelCurrent !== -1 ) { 208 self._changeOption(); 209 self._toggleSelect(); 210 } 211 break; 212 // esc key 213 case 27: 214 ev.preventDefault(); 215 if( self._isOpen() ) { 216 self._toggleSelect(); 217 } 218 break; 219 } 220 } ); 221 } 222 223 /** 224 * navigate with up/dpwn keys 225 */ 226 SelectFx.prototype._navigateOpts = function(dir) { 227 if( !this._isOpen() ) { 228 this._toggleSelect(); 229 } 230 231 var tmpcurrent = typeof this.preSelCurrent != 'undefined' && this.preSelCurrent !== -1 ? this.preSelCurrent : this.current; 232 233 if( dir === 'prev' && tmpcurrent > 0 || dir === 'next' && tmpcurrent < this.selOptsCount - 1 ) { 234 // save pre selected current - if we click on option, or press enter, or press space this is going to be the index of the current option 235 this.preSelCurrent = dir === 'next' ? tmpcurrent + 1 : tmpcurrent - 1; 236 // remove focus class if any.. 237 this._removeFocus(); 238 // add class focus - track which option we are navigating 239 classie.add( this.selOpts[this.preSelCurrent], 'cs-focus' ); 240 } 241 } 242 243 /** 244 * open/close select 245 * when opened show the default placeholder if any 246 */ 247 SelectFx.prototype._toggleSelect = function() { 248 // remove focus class if any.. 249 this._removeFocus(); 250 251 if( this._isOpen() ) { 252 if( this.current !== -1 ) { 253 // update placeholder text 254 this.selPlaceholder.textContent = this.selOpts[ this.current ].textContent; 255 } 256 classie.remove( this.selEl, 'cs-active' ); 257 } 258 else { 259 if( this.hasDefaultPlaceholder && this.options.stickyPlaceholder ) { 260 // everytime we open we wanna see the default placeholder text 261 this.selPlaceholder.textContent = this.selectedOpt.textContent; 262 } 263 classie.add( this.selEl, 'cs-active' ); 264 } 265 } 266 267 /** 268 * change option - the new value is set 269 */ 270 SelectFx.prototype._changeOption = function() { 271 // if pre selected current (if we navigate with the keyboard)... 272 if( typeof this.preSelCurrent != 'undefined' && this.preSelCurrent !== -1 ) { 273 this.current = this.preSelCurrent; 274 this.preSelCurrent = -1; 275 } 276 277 // current option 278 var opt = this.selOpts[ this.current ]; 279 280 // update current selected value 281 this.selPlaceholder.textContent = opt.textContent; 282 283 // change native select element´s value 284 this.el.value = opt.getAttribute( 'data-value' ); 285 286 // remove class cs-selected from old selected option and add it to current selected option 287 var oldOpt = this.selEl.querySelector( 'li.cs-selected' ); 288 if( oldOpt ) { 289 classie.remove( oldOpt, 'cs-selected' ); 290 } 291 classie.add( opt, 'cs-selected' ); 292 293 // if there´s a link defined 294 if( opt.getAttribute( 'data-link' ) ) { 295 // open in new tab? 296 if( this.options.newTab ) { 297 window.open( opt.getAttribute( 'data-link' ), '_blank' ); 298 } 299 else { 300 window.location = opt.getAttribute( 'data-link' ); 301 } 302 } 303 304 // callback 305 this.options.onChange( this.el.value ); 306 } 307 308 /** 309 * returns true if select element is opened 310 */ 311 SelectFx.prototype._isOpen = function(opt) { 312 return classie.has( this.selEl, 'cs-active' ); 313 } 314 315 /** 316 * removes the focus class from the option 317 */ 318 SelectFx.prototype._removeFocus = function(opt) { 319 var focusEl = this.selEl.querySelector( 'li.cs-focus' ) 320 if( focusEl ) { 321 classie.remove( focusEl, 'cs-focus' ); 322 } 323 } 324 325 /** 326 * add to global namespace 327 */ 328 window.SelectFx = SelectFx; 329 330} )( window );
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.