jselect-1.0.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. /*!
  2. * js模拟系统select v1.0
  3. * http://www.cnblogs.com/typeof/
  4. *
  5. * 主流浏览器对html的select元素渲染都不一样,IE系列(6, 7, 8)也不一样,
  6. * firefox,chrome,safari,opera 渲染和事件处理也稍有差异
  7. * 该脚本解决了在不同浏览器下渲染和事件响应不一致的问题,对系统select是完全
  8. * 意义上的替换。v1.0版本只支持单个select选择即不支持二级或者三级联动且不支持系统select的onchange事件。
  9. * 该版本支持模拟select选择的数据和系统select选中数据的同步,不影响form表单的提交
  10. *
  11. * 如果page上select有的不想通过该脚本替换,只想维持系统select,可以在相应的select元素添加自定义属性data-enabled="true",
  12. * 否则可以在想要通过该脚本替换的select元素上添加自定义属性data-enabled="false"或者不加,会默认为这个select需要
  13. * 通过该脚本进行替换
  14. *
  15. * 日期:2012-11-07 15:38
  16. */
  17. (function(squid) {
  18. function JSelect() {
  19. this.init()
  20. }
  21. JSelect.prototype = {
  22. constructor: JSelect,
  23. init: function(context) {
  24. //获取指定上下文所有select元素
  25. var elems = squid.getElementsByTagName('select', context)
  26. this.globalEvent()
  27. this.initView(elems)
  28. },
  29. initView: function(elems) {
  30. var i = 0,
  31. elem,
  32. length = elems.length,
  33. enabled;
  34. for(; i < length; i++) {
  35. elem = elems[i]
  36. enabled = elem.getAttribute('data-enabled')
  37. //使用系统select
  38. if(!enabled || enabled === 'true')
  39. continue
  40. if(squid.isVisible(elem))
  41. elem.style.display = 'none'
  42. this.create(elem)
  43. }
  44. },
  45. create: function(elem) {
  46. var data = [],
  47. i = 0,
  48. length,
  49. option,
  50. options,
  51. value,
  52. text,
  53. obj,
  54. lis,
  55. ul,
  56. _default,
  57. icon,
  58. selectedText,
  59. selectedValue,
  60. div,
  61. wrapper,
  62. position,
  63. left,
  64. top,
  65. cssText;
  66. options = elem.getElementsByTagName('option')
  67. length = options.length
  68. for(; i < length; i++) {
  69. option = options[i]
  70. value = option.value
  71. text = option.innerText || option.textContent
  72. obj = {
  73. value: value,
  74. text: text
  75. }
  76. if(option.selected) {
  77. selectedValue = value
  78. selectedText = text
  79. obj['selected'] = true
  80. }
  81. data.push(obj)
  82. }
  83. lis = this.render(this.tmpl, data)
  84. ul = '<ul class="select-item">' + lis + '</ul>'
  85. //
  86. div = document.createElement('span')
  87. div.style.display = 'none'
  88. div.className = 'select-wrapper'
  89. //已选元素
  90. _default = document.createElement('span')
  91. _default.className = 'select-default unselectable'
  92. _default.unselectable = 'on'
  93. //让div元素能够获取焦点
  94. _default.setAttribute('tabindex', '1')
  95. _default.setAttribute('data-value', selectedValue)
  96. _default.setAttribute('hidefocus', true)
  97. _default.innerHTML = selectedText
  98. div.appendChild(_default)
  99. //选择icon
  100. icon = document.createElement('i')
  101. icon.className = 'select-icon'
  102. div.appendChild(icon)
  103. //下拉列表
  104. wrapper = document.createElement('span')
  105. wrapper.className = 'select-list hide'
  106. wrapper.innerHTML = ul
  107. //生成新的元素
  108. div.appendChild(wrapper)
  109. //插入到select元素后面
  110. elem.parentNode.insertBefore(div, null)
  111. //获取select元素left top值
  112. //先设置select显示,取完left, top值后重新隐藏
  113. elem.style.display = 'block'
  114. //事件绑定
  115. this.sysEvent(div)
  116. position = squid.position(elem)
  117. elem.style.display = 'none'
  118. left = position.left
  119. top = position.top
  120. cssText = ' display: inline-block;'
  121. div.style.cssText = cssText
  122. },
  123. globalEvent: function() {
  124. //document 添加click事件,用户处理每个jselect元素展开关闭
  125. var target,
  126. className,
  127. elem,
  128. wrapper,
  129. status,
  130. that = this;
  131. squid.on(document, 'click', function(event) {
  132. target = event.target,
  133. className = target.className;
  134. switch(className) {
  135. case 'select-icon':
  136. case 'select-default unselectable':
  137. elem = target.tagName.toLowerCase() === 'span' ? target : target.previousSibling
  138. wrapper = elem.nextSibling.nextSibling
  139. //firefox 鼠标右键会触发click事件
  140. //鼠标左键点击执行
  141. if(event.button === 0) {
  142. //初始化选中元素
  143. that.initSelected(elem)
  144. if(squid.isHidden(wrapper)) {
  145. status = 'inline-block'
  146. //关闭所有展开jselect
  147. that.closeSelect()
  148. }else{
  149. status = 'none'
  150. }
  151. wrapper.style.display = status
  152. elem.focus()
  153. }else if(event.button === 2){
  154. wrapper.style.display = 'none'
  155. }
  156. that.zIndex(wrapper)
  157. break
  158. case 'select-option':
  159. case 'select-option selected':
  160. if(event.button === 0) {
  161. that.fireSelected(target, target.parentNode.parentNode.previousSibling.previousSibling)
  162. wrapper.style.display = 'none'
  163. }
  164. break
  165. default:
  166. while(target && target.nodeType !== 9) {
  167. if(target.nodeType === 1) {
  168. if(target.className === 'select-wrapper') {
  169. return
  170. }
  171. }
  172. target = target.parentNode
  173. }
  174. that.closeSelect()
  175. break
  176. }
  177. })
  178. },
  179. sysEvent: function(elem) {
  180. var stand = elem.firstChild,
  181. dropdown = elem.lastChild,
  182. target,
  183. //firefox = 'MozBinding' in document.documentElement.style,
  184. chrome = /chrome/i.test(navigator.userAgent),
  185. keyup = chrome ? 'keypress' : 'keyup',
  186. that = this;
  187. squid.on(elem, 'mouseover', function(event) {
  188. if(!that.doScrolling) {
  189. target = event.target
  190. that.activate(target)
  191. }
  192. })
  193. squid.on(elem, 'mouseout', function(event) {
  194. if(!that.doScrolling) {
  195. target = event.target
  196. that.deactivate(target)
  197. }
  198. })
  199. squid.on(stand, 'keydown', function(event) {
  200. var keyCode = event.keyCode;
  201. switch(keyCode) {
  202. //回车选中
  203. case 13:
  204. that.enter(dropdown)
  205. break
  206. //向上键
  207. case 38:
  208. that.doScrolling = true
  209. that.up(dropdown)
  210. break
  211. //向下键
  212. case 40:
  213. that.doScrolling = true
  214. that.down(dropdown)
  215. break
  216. default:
  217. break
  218. }
  219. })
  220. squid.on(stand, keyup, function(event) {
  221. var keyCode = event.keyCode;
  222. switch(keyCode) {
  223. //回车选中
  224. case 13:
  225. that.doScrolling = false
  226. break
  227. //向上键
  228. case 38:
  229. that.doScrolling = false
  230. break
  231. //向下键
  232. case 40:
  233. that.doScrolling = false
  234. break
  235. default:
  236. break
  237. }
  238. })
  239. },
  240. zIndex: function(elem) {
  241. var index = 10,
  242. cur = elem.parentNode.parentNode,
  243. next = squid.next(cur);
  244. if(next) {
  245. cur.style.zIndex = index
  246. next.style.zIndex = --index
  247. }
  248. },
  249. initSelected: function(elem) {
  250. var curText = elem.innerText || elem.textContent,
  251. curValue = elem.getAttribute('data-value'),
  252. wrapper = elem.nextSibling.nextSibling,
  253. n = wrapper.firstChild.firstChild,
  254. text,
  255. value,
  256. dir,
  257. min = 0,
  258. max,
  259. hidden = false;
  260. for(; n; n = n.nextSibling) {
  261. text = n.innerText || n.textContent
  262. value = n.getAttribute('data-value')
  263. if(curText === text && curValue === value) {
  264. //显示已选中元素
  265. if(squid.isHidden(wrapper)) {
  266. wrapper.style.display = 'block'
  267. hidden = true
  268. }
  269. max = wrapper.scrollHeight
  270. if(n.offsetTop > (max / 2)) {
  271. if(wrapper.clientHeight + wrapper.scrollTop === max)
  272. dir = 'up'
  273. else
  274. dir = 'down'
  275. }else{
  276. if(wrapper.scrollTop === min)
  277. dir = 'down'
  278. else
  279. dir = 'up'
  280. }
  281. this.inView(n, wrapper, dir)
  282. if(hidden)
  283. wrapper.style.display = 'none'
  284. this.activate(n)
  285. break
  286. }
  287. }
  288. },
  289. activate: function(elem) {
  290. var tagName = (elem.tagName || '').toLowerCase(),
  291. className = elem.className,
  292. parent = elem.parentNode,
  293. first = parent.firstChild,
  294. last = parent.lastChild;
  295. switch(tagName) {
  296. case 'li':
  297. //li.select-option 元素
  298. if(!~className.indexOf('selected') && (elem !== first || elem !== last)) {
  299. this.deactivate(elem)
  300. elem.className = className + ' selected'
  301. }
  302. break
  303. default:
  304. break
  305. }
  306. },
  307. deactivate: function(elem) {
  308. var tagName = (elem.tagName || '').toLowerCase(),
  309. className = (' ' + elem.className + ' ').replace(/[\n\r\t]/, '');
  310. switch(tagName) {
  311. case 'li':
  312. //li.select-option 元素
  313. var i = 0,
  314. lis = squid.siblings(elem),
  315. length = lis.length,
  316. cur;
  317. for(; i < length; i++) {
  318. cur = lis[i]
  319. cur.className = squid.trim(className.replace(' selected ', ''))
  320. }
  321. break
  322. default:
  323. break
  324. }
  325. },
  326. fireSelected: function(elem, s) {
  327. var text = elem.innerText || elem.textContent,
  328. value = elem.getAttribute('data-value'),
  329. r;
  330. s.setAttribute('data-value', value)
  331. if(s.innerText)
  332. s.innerText = text
  333. else
  334. s.textContent = text
  335. //触发系统select选中,用于form表单提交
  336. r = s.parentNode.previousSibling.previousSibling
  337. r.value = value
  338. r.setAttribute('data-text', text)
  339. },
  340. closeSelect: function() {
  341. var elems = squid.getElementsByClassName('select-list'),
  342. i = 0,
  343. length = elems.length,
  344. elem;
  345. for(; i < length; i++) {
  346. elem = elems[i]
  347. if(squid.isVisible(elem))
  348. elem.style.display = 'none'
  349. }
  350. },
  351. up: function(elem) {
  352. var ul = elem.firstChild,
  353. lis = ul.childNodes,
  354. li = this.getSelectedIndex(lis),
  355. cur,
  356. i = li.index;
  357. if(i > 0) {
  358. i--
  359. cur = lis[i]
  360. //判断元素是否in view
  361. this.inView(cur, elem, 'up')
  362. this.activate(cur)
  363. this.fireSelected(cur, elem.previousSibling.previousSibling)
  364. }
  365. },
  366. down: function(elem) {
  367. var ul = elem.firstChild,
  368. lis = ul.childNodes,
  369. li = this.getSelectedIndex(lis),
  370. cur,
  371. i = li.index;
  372. if(i < lis.length - 1) {
  373. i++
  374. cur = lis[i]
  375. //判断元素是否in view
  376. this.inView(cur, elem, 'down')
  377. this.activate(cur)
  378. this.fireSelected(cur, elem.previousSibling.previousSibling)
  379. }
  380. },
  381. enter: function(elem) {
  382. var ul = elem.firstChild,
  383. lis = ul.childNodes,
  384. li,
  385. i,
  386. cur;
  387. li = this.getSelectedIndex(lis)
  388. i = li.index
  389. cur = lis[i]
  390. this.fireSelected(cur, elem.previousSibling.previousSibling)
  391. this.closeSelect()
  392. },
  393. getSelectedIndex: function(elems) {
  394. var i = 0,
  395. length = elems.length,
  396. elem;
  397. for(; i < length; i++) {
  398. elem = elems[i]
  399. if(~elem.className.indexOf('selected')) {
  400. return {
  401. index: i
  402. }
  403. }
  404. }
  405. return {
  406. index: -1
  407. }
  408. },
  409. inView: function(elem, wrapper, dir) {
  410. var scrollTop = wrapper.scrollTop,
  411. offsetTop = elem.offsetTop,
  412. top;
  413. if(dir === 'up') {
  414. if(offsetTop === 0) {
  415. wrapper.scrollTop = offsetTop;
  416. }else if(offsetTop < scrollTop) {
  417. top = offsetTop - scrollTop
  418. this.scrollInView(wrapper, top)
  419. }
  420. }else{
  421. var clientHeight = wrapper.clientHeight;
  422. if(offsetTop + elem.offsetHeight === wrapper.scrollHeight) {
  423. wrapper.scrollTop = wrapper.scrollHeight - wrapper.clientHeight
  424. }else if(offsetTop + elem.offsetHeight > clientHeight + scrollTop) {
  425. top = (offsetTop + elem.offsetHeight) - (scrollTop + clientHeight)
  426. this.scrollInView(wrapper, top)
  427. }
  428. }
  429. },
  430. scrollInView: function(elem, top) {
  431. setTimeout(function() {
  432. elem.scrollTop += top
  433. }, 10)
  434. },
  435. doScrolling: false,
  436. render: function(tmpl, data) {
  437. var i = 0,
  438. cur,
  439. length = data.length,
  440. prop,
  441. value,
  442. item,
  443. r = [];
  444. for(; i < length; i++) {
  445. cur = data[i]
  446. item = tmpl.replace(/\{\{\w+\}\}/g, function(a) {
  447. prop = a.replace(/[\{\}]+/g, '')
  448. value = cur[prop] || ''
  449. if(prop === 'class') {
  450. value += 'select-option'
  451. if(cur.selected) {
  452. value += ' selected'
  453. }
  454. }
  455. return value
  456. })
  457. r.push(item)
  458. }
  459. return r.join('')
  460. },
  461. tmpl: '<li class="{{class}}" data-value="{{value}}">{{text}}</li>'
  462. }
  463. squid.swing.jselect = function() {
  464. return new JSelect()
  465. }
  466. })(squid);