/*!
* 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 = '
'
//
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: '{{text}}'
}
squid.swing.jselect = function() {
return new JSelect()
}
})(squid);