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