reprogl

Форк
0
/
select-fx.js 
325 строк · 10.6 Кб
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
            $(this.selOpts[this.preSelCurrent]).addClass('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
            $(this.selEl).removeClass('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
            $(this.selEl).addClass('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
        // update current selected value
276
        this.selPlaceholder.textContent = opt.textContent;
277

278
        // change native select element´s value
279
        this.el.value = opt.getAttribute( 'data-value' );
280

281
        // remove class cs-selected from old selected option and add it to current selected option
282
        var oldOpt = this.selEl.querySelector( 'li.cs-selected' );
283
        if( oldOpt ) {
284
            $(oldOpt).removeClass('cs-selected');
285
        }
286
        $(opt).addClass('cs-selected');
287

288
        // if there´s a link defined
289
        if( opt.getAttribute( 'data-link' ) ) {
290
            // open in new tab?
291
            if( this.options.newTab ) {
292
                window.open( opt.getAttribute( 'data-link' ), '_blank' );
293
            }
294
            else {
295
                window.location = opt.getAttribute( 'data-link' );
296
            }
297
        }
298

299
        // callback
300
        this.options.onChange( this.el.value );
301
    }
302

303
    /**
304
     * returns true if select element is opened
305
     */
306
    SelectFx.prototype._isOpen = function(opt) {
307
        return $(this.selEl).hasClass('cs-active');
308
    }
309

310
    /**
311
     * removes the focus class from the option
312
     */
313
    SelectFx.prototype._removeFocus = function(opt) {
314
        var focusEl = this.selEl.querySelector( 'li.cs-focus' )
315
        if( focusEl ) {
316
            $(focusEl).removeClass('cs-focus' );
317
        }
318
    }
319

320
    /**
321
     * add to global namespace
322
     */
323
    window.SelectFx = SelectFx;
324

325
} )( window );
326

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.