501 lines
17 KiB
JavaScript
501 lines
17 KiB
JavaScript
/*!
|
||
* js模拟系统select v1.0
|
||
* http://www.cnblogs.com/typeof/
|
||
*
|
||
* 主流浏览器对html的select元素渲染都不一样,IE系列(6, 7, 8)也不一样,
|
||
* firefox,chrome,safari,opera 渲染和事件处理也稍有差异
|
||
* 该脚本解决了在不同浏览器下渲染和事件响应不一致的问题,对系统select是完全
|
||
* 意义上的替换。v1.0版本只支持单个select选择即不支持二级或者三级联动且不支持系统select的onchange事件。
|
||
* 该版本支持模拟select选择的数据和系统select选中数据的同步,不影响form表单的提交
|
||
*
|
||
* 如果page上select有的不想通过该脚本替换,只想维持系统select,可以在相应的select元素添加自定义属性data-enabled="true",
|
||
* 否则可以在想要通过该脚本替换的select元素上添加自定义属性data-enabled="false"或者不加,会默认为这个select需要
|
||
* 通过该脚本进行替换
|
||
*
|
||
* 日期:2012-11-07 15:38
|
||
*/
|
||
(function(squid) {
|
||
function JSelect() {
|
||
this.init()
|
||
}
|
||
|
||
JSelect.prototype = {
|
||
constructor: JSelect,
|
||
init: function(context) {
|
||
//获取指定上下文所有select元素
|
||
var elems = squid.getElementsByTagName('select', context)
|
||
this.globalEvent()
|
||
this.initView(elems)
|
||
},
|
||
initView: function(elems) {
|
||
var i = 0,
|
||
elem,
|
||
length = elems.length,
|
||
enabled;
|
||
|
||
for(; i < length; i++) {
|
||
elem = elems[i]
|
||
enabled = elem.getAttribute('data-enabled')
|
||
//使用系统select
|
||
if(!enabled || enabled === 'true')
|
||
continue
|
||
if(squid.isVisible(elem))
|
||
elem.style.display = 'none'
|
||
|
||
this.create(elem)
|
||
}
|
||
},
|
||
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 = elem.getElementsByTagName('option')
|
||
length = options.length
|
||
for(; i < length; i++) {
|
||
option = options[i]
|
||
value = option.value
|
||
text = option.innerText || option.textContent
|
||
|
||
obj = {
|
||
value: value,
|
||
text: text
|
||
}
|
||
if(option.selected) {
|
||
selectedValue = value
|
||
selectedText = text
|
||
obj['selected'] = true
|
||
}
|
||
data.push(obj)
|
||
}
|
||
|
||
lis = this.render(this.tmpl, data)
|
||
ul = '<ul class="select-item">' + lis + '</ul>'
|
||
//
|
||
div = document.createElement('span')
|
||
div.style.display = 'none'
|
||
div.className = 'select-wrapper'
|
||
//已选元素
|
||
_default = document.createElement('span')
|
||
_default.className = 'select-default unselectable'
|
||
_default.unselectable = 'on'
|
||
//让div元素能够获取焦点
|
||
_default.setAttribute('tabindex', '1')
|
||
_default.setAttribute('data-value', selectedValue)
|
||
_default.setAttribute('hidefocus', true)
|
||
_default.innerHTML = selectedText
|
||
div.appendChild(_default)
|
||
//选择icon
|
||
icon = document.createElement('i')
|
||
icon.className = 'select-icon'
|
||
div.appendChild(icon)
|
||
//下拉列表
|
||
wrapper = document.createElement('span')
|
||
wrapper.className = 'select-list hide'
|
||
wrapper.innerHTML = ul
|
||
//生成新的元素
|
||
div.appendChild(wrapper)
|
||
//插入到select元素后面
|
||
elem.parentNode.insertBefore(div, null)
|
||
//获取select元素left top值
|
||
//先设置select显示,取完left, top值后重新隐藏
|
||
elem.style.display = 'block'
|
||
//事件绑定
|
||
this.sysEvent(div)
|
||
position = squid.position(elem)
|
||
elem.style.display = 'none'
|
||
left = position.left
|
||
top = position.top
|
||
cssText = ' display: inline-block;'
|
||
div.style.cssText = cssText
|
||
},
|
||
globalEvent: function() {
|
||
//document 添加click事件,用户处理每个jselect元素展开关闭
|
||
var target,
|
||
className,
|
||
elem,
|
||
wrapper,
|
||
status,
|
||
that = this;
|
||
|
||
|
||
squid.on(document, 'click', function(event) {
|
||
target = event.target,
|
||
className = target.className;
|
||
|
||
switch(className) {
|
||
case 'select-icon':
|
||
case 'select-default unselectable':
|
||
elem = target.tagName.toLowerCase() === 'span' ? target : target.previousSibling
|
||
wrapper = elem.nextSibling.nextSibling
|
||
|
||
//firefox 鼠标右键会触发click事件
|
||
//鼠标左键点击执行
|
||
if(event.button === 0) {
|
||
//初始化选中元素
|
||
that.initSelected(elem)
|
||
if(squid.isHidden(wrapper)) {
|
||
status = 'inline-block'
|
||
//关闭所有展开jselect
|
||
that.closeSelect()
|
||
}else{
|
||
status = 'none'
|
||
}
|
||
wrapper.style.display = status
|
||
elem.focus()
|
||
}else if(event.button === 2){
|
||
wrapper.style.display = 'none'
|
||
}
|
||
that.zIndex(wrapper)
|
||
break
|
||
case 'select-option':
|
||
case 'select-option selected':
|
||
if(event.button === 0) {
|
||
that.fireSelected(target, target.parentNode.parentNode.previousSibling.previousSibling)
|
||
wrapper.style.display = 'none'
|
||
}
|
||
break
|
||
default:
|
||
while(target && target.nodeType !== 9) {
|
||
if(target.nodeType === 1) {
|
||
if(target.className === 'select-wrapper') {
|
||
return
|
||
}
|
||
}
|
||
target = target.parentNode
|
||
}
|
||
that.closeSelect()
|
||
break
|
||
}
|
||
})
|
||
},
|
||
sysEvent: function(elem) {
|
||
var stand = elem.firstChild,
|
||
dropdown = elem.lastChild,
|
||
target,
|
||
//firefox = 'MozBinding' in document.documentElement.style,
|
||
chrome = /chrome/i.test(navigator.userAgent),
|
||
keyup = chrome ? 'keypress' : 'keyup',
|
||
that = this;
|
||
|
||
squid.on(elem, 'mouseover', function(event) {
|
||
if(!that.doScrolling) {
|
||
target = event.target
|
||
that.activate(target)
|
||
}
|
||
})
|
||
|
||
squid.on(elem, 'mouseout', function(event) {
|
||
if(!that.doScrolling) {
|
||
target = event.target
|
||
that.deactivate(target)
|
||
}
|
||
})
|
||
|
||
squid.on(stand, 'keydown', function(event) {
|
||
var keyCode = event.keyCode;
|
||
|
||
switch(keyCode) {
|
||
//回车选中
|
||
case 13:
|
||
that.enter(dropdown)
|
||
break
|
||
//向上键
|
||
case 38:
|
||
that.doScrolling = true
|
||
that.up(dropdown)
|
||
break
|
||
//向下键
|
||
case 40:
|
||
that.doScrolling = true
|
||
that.down(dropdown)
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
})
|
||
|
||
squid.on(stand, keyup, function(event) {
|
||
var keyCode = event.keyCode;
|
||
|
||
switch(keyCode) {
|
||
//回车选中
|
||
case 13:
|
||
that.doScrolling = false
|
||
break
|
||
//向上键
|
||
case 38:
|
||
that.doScrolling = false
|
||
break
|
||
//向下键
|
||
case 40:
|
||
that.doScrolling = false
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
})
|
||
},
|
||
zIndex: function(elem) {
|
||
var index = 10,
|
||
cur = elem.parentNode.parentNode,
|
||
next = squid.next(cur);
|
||
|
||
if(next) {
|
||
cur.style.zIndex = index
|
||
next.style.zIndex = --index
|
||
}
|
||
},
|
||
initSelected: function(elem) {
|
||
var curText = elem.innerText || elem.textContent,
|
||
curValue = elem.getAttribute('data-value'),
|
||
wrapper = elem.nextSibling.nextSibling,
|
||
n = wrapper.firstChild.firstChild,
|
||
text,
|
||
value,
|
||
dir,
|
||
min = 0,
|
||
max,
|
||
hidden = false;
|
||
|
||
for(; n; n = n.nextSibling) {
|
||
text = n.innerText || n.textContent
|
||
value = n.getAttribute('data-value')
|
||
if(curText === text && curValue === value) {
|
||
//显示已选中元素
|
||
if(squid.isHidden(wrapper)) {
|
||
wrapper.style.display = 'block'
|
||
hidden = true
|
||
}
|
||
max = wrapper.scrollHeight
|
||
if(n.offsetTop > (max / 2)) {
|
||
if(wrapper.clientHeight + wrapper.scrollTop === max)
|
||
dir = 'up'
|
||
else
|
||
dir = 'down'
|
||
}else{
|
||
if(wrapper.scrollTop === min)
|
||
dir = 'down'
|
||
else
|
||
dir = 'up'
|
||
}
|
||
this.inView(n, wrapper, dir)
|
||
if(hidden)
|
||
wrapper.style.display = 'none'
|
||
this.activate(n)
|
||
break
|
||
}
|
||
}
|
||
},
|
||
activate: function(elem) {
|
||
var tagName = (elem.tagName || '').toLowerCase(),
|
||
className = elem.className,
|
||
parent = elem.parentNode,
|
||
first = parent.firstChild,
|
||
last = parent.lastChild;
|
||
|
||
switch(tagName) {
|
||
case 'li':
|
||
//li.select-option 元素
|
||
if(!~className.indexOf('selected') && (elem !== first || elem !== last)) {
|
||
this.deactivate(elem)
|
||
elem.className = className + ' selected'
|
||
}
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
},
|
||
deactivate: function(elem) {
|
||
var tagName = (elem.tagName || '').toLowerCase(),
|
||
className = (' ' + elem.className + ' ').replace(/[\n\r\t]/, '');
|
||
|
||
switch(tagName) {
|
||
case 'li':
|
||
//li.select-option 元素
|
||
var i = 0,
|
||
lis = squid.siblings(elem),
|
||
length = lis.length,
|
||
cur;
|
||
|
||
for(; i < length; i++) {
|
||
cur = lis[i]
|
||
cur.className = squid.trim(className.replace(' selected ', ''))
|
||
}
|
||
break
|
||
default:
|
||
break
|
||
}
|
||
},
|
||
fireSelected: function(elem, s) {
|
||
var text = elem.innerText || elem.textContent,
|
||
value = elem.getAttribute('data-value'),
|
||
r;
|
||
|
||
s.setAttribute('data-value', value)
|
||
if(s.innerText)
|
||
s.innerText = text
|
||
else
|
||
s.textContent = text
|
||
|
||
//触发系统select选中,用于form表单提交
|
||
r = s.parentNode.previousSibling.previousSibling
|
||
r.value = value
|
||
r.setAttribute('data-text', text)
|
||
},
|
||
closeSelect: function() {
|
||
var elems = squid.getElementsByClassName('select-list'),
|
||
i = 0,
|
||
length = elems.length,
|
||
elem;
|
||
|
||
for(; i < length; i++) {
|
||
elem = elems[i]
|
||
if(squid.isVisible(elem))
|
||
elem.style.display = 'none'
|
||
}
|
||
},
|
||
up: function(elem) {
|
||
var ul = elem.firstChild,
|
||
lis = ul.childNodes,
|
||
li = this.getSelectedIndex(lis),
|
||
cur,
|
||
i = li.index;
|
||
|
||
if(i > 0) {
|
||
i--
|
||
cur = lis[i]
|
||
//判断元素是否in view
|
||
this.inView(cur, elem, 'up')
|
||
this.activate(cur)
|
||
this.fireSelected(cur, elem.previousSibling.previousSibling)
|
||
}
|
||
},
|
||
down: function(elem) {
|
||
var ul = elem.firstChild,
|
||
lis = ul.childNodes,
|
||
li = this.getSelectedIndex(lis),
|
||
cur,
|
||
i = li.index;
|
||
|
||
if(i < lis.length - 1) {
|
||
i++
|
||
cur = lis[i]
|
||
//判断元素是否in view
|
||
this.inView(cur, elem, 'down')
|
||
this.activate(cur)
|
||
this.fireSelected(cur, elem.previousSibling.previousSibling)
|
||
}
|
||
},
|
||
enter: function(elem) {
|
||
var ul = elem.firstChild,
|
||
lis = ul.childNodes,
|
||
li,
|
||
i,
|
||
cur;
|
||
|
||
li = this.getSelectedIndex(lis)
|
||
i = li.index
|
||
cur = lis[i]
|
||
this.fireSelected(cur, elem.previousSibling.previousSibling)
|
||
this.closeSelect()
|
||
},
|
||
getSelectedIndex: function(elems) {
|
||
var i = 0,
|
||
length = elems.length,
|
||
elem;
|
||
|
||
for(; i < length; i++) {
|
||
elem = elems[i]
|
||
if(~elem.className.indexOf('selected')) {
|
||
return {
|
||
index: i
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
index: -1
|
||
}
|
||
},
|
||
inView: function(elem, wrapper, dir) {
|
||
var scrollTop = wrapper.scrollTop,
|
||
offsetTop = elem.offsetTop,
|
||
top;
|
||
|
||
if(dir === 'up') {
|
||
if(offsetTop === 0) {
|
||
wrapper.scrollTop = offsetTop;
|
||
}else if(offsetTop < scrollTop) {
|
||
top = offsetTop - scrollTop
|
||
this.scrollInView(wrapper, top)
|
||
}
|
||
}else{
|
||
var clientHeight = wrapper.clientHeight;
|
||
|
||
if(offsetTop + elem.offsetHeight === wrapper.scrollHeight) {
|
||
wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight
|
||
}else if(offsetTop + elem.offsetHeight > clientHeight + scrollTop) {
|
||
top = (offsetTop + elem.offsetHeight) - (scrollTop + clientHeight)
|
||
this.scrollInView(wrapper, top)
|
||
}
|
||
}
|
||
},
|
||
scrollInView: function(elem, top) {
|
||
setTimeout(function() {
|
||
elem.scrollTop += top
|
||
}, 10)
|
||
},
|
||
doScrolling: false,
|
||
render: function(tmpl, data) {
|
||
var i = 0,
|
||
cur,
|
||
length = data.length,
|
||
prop,
|
||
value,
|
||
item,
|
||
r = [];
|
||
|
||
for(; i < length; i++) {
|
||
cur = data[i]
|
||
item = tmpl.replace(/\{\{\w+\}\}/g, function(a) {
|
||
prop = a.replace(/[\{\}]+/g, '')
|
||
value = cur[prop] || ''
|
||
if(prop === 'class') {
|
||
value += 'select-option'
|
||
if(cur.selected) {
|
||
value += ' selected'
|
||
}
|
||
}
|
||
|
||
return value
|
||
})
|
||
r.push(item)
|
||
}
|
||
|
||
return r.join('')
|
||
},
|
||
tmpl: '<li class="{{class}}" data-value="{{value}}">{{text}}</li>'
|
||
}
|
||
|
||
squid.swing.jselect = function() {
|
||
return new JSelect()
|
||
}
|
||
})(squid); |