SoFunction
Updated on 2025-03-01

JavaScript simulation method implementation of select, jselect

Since mainstream browsers render different select elements, the display is different in each browser. The most important thing is that by default the UI is too rough, and even if it is beautified through CSS, it cannot achieve a very beautiful effect. This is intolerable to us front-end developers who are focused on UX. So when the project is not too busy, I plan to write a simulated select control. Next, I will share with you the details of the implementation, the problems encountered, and how to use them.
1. Implementation details
init: function(context) {
//Get all select elements in the specified context
var elems = ('select', context)
()
(elems)
}
In an application scenario where a user is registered, there are multiple select elements. The simulated select control (hereinafter referred to as jselect) initialization method will get all select elements on the page, and then bind the global event globalEvent, and the initialization page will display the initView. The globalEvent method is as follows:
Copy the codeThe code is as follows:

globalEvent: function() {
//document Add click event, the user handles each jselect element to expand and close
var target,
className,
elem,
wrapper,
status,
that = this;

(document, 'click', function(event) {
target = ,
className = ;

switch(className) {
case 'select-icon':
case 'select-default unselectable':
elem = () === 'div' ? target :
wrapper =

//The right mouse button of the firefox will trigger the click event
//Click the left mouse button to execute
if( === 0) {
//Initialize the selected element
(elem)
if((wrapper)) {
status = 'block'
//Close all expanded jselect
()
}else{
status = 'none'
}
= status
()
}else if( === 2){
= 'none'
}
(wrapper)
break
case 'select-option':
case 'select-option selected':
if( === 0) {
(target, )
= 'none'
}
break
default:
while(target && !== 9) {
if( === 1) {
if( === 'select-wrapper') {
return
}
}
target =
}
()
break
}
})
}

globalEvent implements the binding of the click event in the document, and then when the click event is triggered on the page, it uses the event proxy to determine whether the current click element is the target element that needs to be processed. The judgment condition is to pass the element class. The branches of the statements in the code are: expand the currently clicked jselect element drop-down, select the click list item, and determine whether jselect needs to be closed.

The initView method is as follows:
Copy the codeThe code is as follows:

initView: function(elems) {
var i = 0,
elem,
length = ,
enabled;

for(; i < length; i++) {
elem = elems[i]
enabled = ('data-enabled')
//Use the system select
if(!enabled || enabled === 'true')
continue
if((elem))
= 'none'

(elem)
}
}

initView implements the select element that needs to be replaced with jselect first hides and then calls the create method, generates the overall structure of a single jselect and inserts it into the page and replaces the default select position.

The create method is as follows:
Copy the codeThe code is as follows:

create: function(elem) {
var data = [],
i = 0,
length,
option,
options,
value,
text,
obj,
lis,
ul,
_default,
icon,
selectedText,
selectedValue,
div,
wrapper,
position,
left,
top,
cssText;

options = ('option')
length =
for(; i < length; i++) {
option = options[i]
value =
text = ||

obj = {
value: value,
text: text
}
if() {
selectedValue = value
selectedText = text
obj['selected'] = true
}
(obj)
}

lis = (, data)
ul = '<ul class="select-item">' + lis + '</ul>'
//
div = ('div')
= 'none'
= 'select-wrapper'
//Selected elements
_default = ('div')
_default.className = 'select-default unselectable'
_default.unselectable = 'on'
//Let the div element get focus
_default.setAttribute('tabindex', '1')
_default.setAttribute('data-value', selectedValue)
_default.setAttribute('hidefocus', true)
_default.innerHTML = selectedText
(_default)
//Select icon
icon = ('span')
= 'select-icon'
(icon)
//Drop down list
wrapper = ('div')
= 'select-list hide'
= ul
//Generate new elements
(wrapper)
//Insert behind the select element
(div, null)
//Get the left top value of the select element
//Set the select display first, and hide it again after taking the left and top value.
= 'block'
// Event binding
(div)
position = (elem)
= 'none'
left =
top =
cssText = 'left: ' + left + 'px; top: ' + top + 'px; display: block;'
= cssText
}

The create method implements copying the system select data to the jselect drop-down list. The jselect hierarchy relationship is that the outermost layer contains an element with a class select-wrapper, which contains an element with a class select-default that is used to store the selected elements. The element with a class select-icon user tells the user that this is a drop-down list. The div element with a class select-list contains an ul element. The text and value of the option copied from the system select are stored in the text and data-value attributes of the li element respectively. The sysEvent method is to add a click to expand to close the drop-down list event for jselect and select the drop-down element up and down keyboard to enter to select the drop-down element event. The method is used to obtain the position of the system select element relative to its offsetParent, which is different from the offset of the system select element. In fact, it is to get your own offset to get the top, left value, and then subtract the top and left value of the offset obtained by offsetParent respectively. Finally, insert jselect behind the system select element and display it to the page.

The basic process of jselect creation is as described above, and the rest is the implementation of details, for example: click to expand and drop down to display the selected element last time. The specific implementation of this function is the initSelected method as follows
Copy the codeThe code is as follows:

initSelected: function(elem) {
var curText = || ,
curValue = ('data-value'),
wrapper = ,
n = ,
text,
value,
dir,
min = 0,
max,
hidden = false;

for(; n; n = ) {
text = ||
value = ('data-value')
if(curText === text && curValue === value) {
//Show selected elements
if((wrapper)) {
= 'block'
hidden = true
}
max =
if( > (max / 2)) {
if( + === max)
dir = 'up'
else
dir = 'down'
}else{
if( === min)
dir = 'down'
else
dir = 'up'
}
(n, wrapper, dir)
if(hidden)
= 'none'
(n)
break
}
}
}

This method receives a div element with a class select-default that is used to store the elements that the user has selected. The specific implementation method is to first traverse all options to obtain the selected li element of the class, and mark it as the currently selected element through the activate method. There is a place to calculate here, which is to scroll the selected element to the page viewing area every time you expand the drop-down list. Because it is possible that the list content is a lot, but the outer layer of the select-list of the drop-down list will have a maximum height. If the maximum height is exceeded, the scroll bar will appear. If the calculation is not done by default, the selected element may be under the scroll bar or above the scroll bar. Therefore, the position of the container scroll bar needs to be reset through calculation. Specifically, whether the selected content is displayed to the top or bottom of the scroll bar, it is necessary to display the selected element to the viewing area based on whether the offsetTop value of the selected element is greater than half the actual height of the outer container select-list. InView method is as follows
Copy the codeThe code is as follows:

inView: function(elem, wrapper, dir) {
var scrollTop = ,
//The selected element offsetTop
offsetTop = ,
top;

if(dir === 'up') {
if(offsetTop === 0) {
//Scroll bar top
= offsetTop;
}else if(offsetTop < scrollTop) {
top = offsetTop - scrollTop
//Scroll bar to top value
(wrapper, top)
}
}else{
var clientHeight = ;

if(offsetTop + === ) {
= -
}else if(offsetTop + > clientHeight + scrollTop) {
top = (offsetTop + ) - (scrollTop + clientHeight)
(wrapper, top)
}
}
}

The inView method needs to determine whether to scroll up or down. The scrollInView method code is very simple, set the scrollTop of the outer container of the drop-down list to the specified value. The method is implemented as follows
Copy the codeThe code is as follows:

scrollInView: function(elem, top) {
setTimeout(function() {
+= top
}, 10)
}

This method is implemented in setTimeout and adds it to the JavaScript execution queue. The main solution is that the scrollbar expanded to the drop-down list under IE8 will eventually scroll to the top, ignoring the scrollTop set by the code (from a performance perspective, it seems that the settings of scrollTop can also take effect, but in the end, the scrollbar will be reset to the top. I don’t know why IE8 has this problem.), and the selected elements cannot be displayed in the visual area range, and there will be no problem in other browsers.
There are roughly so many implementation details. The logic of closing the drop-down list is very simple.
Problems encountered
How to get the div to get the focus to respond to the keyboard keydown, keyup, keypress events? To Google (Google is not easy to use in extraordinary times, there is no way to make this our feature) I finally found that I need to set the tabindex attribute for the div element, so that the div element can get the focus to respond to the user's operations. Because the browser double-clicks or clicks too frequently by default, the current area will be selected. In order to cancel this default operation to give the user a good experience, it is necessary to add an attribute unselectable to the div element. However, this attribute can only be applied to the IE browser. In other browsers, you can avoid this problem by adding a class name that is unselectable. Other problems are logical control, and some position calculations are not discussed here.
How to use
First, in the page template, hide or do not do any processing. By default, jselect will get all selects on the page in sequence. If you do not want jselect to replace the select elements.
You need to add a custom property data-enabled="true". Of course, adding data-enabled="false" will be replaced by jselect as without this custom property. During the use process, there may be other problems with pages with complex layout structures, because the page structure I tested is very simple, so it may not be tested.
Using jselect requires first import, and then import jselect-1., jselect-1. files. Initialize jselect in the following calling method where jselect needs to be called: ();
Note: jselect source code and demo can be passedDownload here