|
@@ -0,0 +1,501 @@
|
|
|
+/*!
|
|
|
+ * 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);
|