      1 ﻿
      2 /**
      3  * @name	CeL 下拉式表單 function
      4  * @fileoverview
      5  * 本檔案包含了下拉選擇式表單的 functions。
      6  * @since
      7  */
      8
      9 /*
     10 TODO:
     11 HTML 5 <datalist> Tag
     12 date
     13 http://plugins.jquery.com/project/timepicker
     14 http://digitalbush.com/projects/masked-input-plugin/
     15 理想:
     16 http://gs.statcounter.com/
     17
     18
     19 http://plugins.jquery.com/search/node/Autocomplete+type%3Aproject_project
     20 http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/
     21 	http://jsgears.com/thread-114-1-1.html
     22
     23 set focus/blue background-image instead of HTML 5 placeholder text
     24 	http://dev.w3.org/html5/spec/Overview.html#the-placeholder-attribute
     25 	The placeholder attribute represents a short hint (a word or short phrase) intended to aid the user with data entry.
     26 e.g., background-image: url(http://www.google.com/cse/intl/en/images/google_custom_search_watermark.gif); background-attachment: initial; background-origin: initial; background-clip: initial; background-color: rgb(255, 255, 255); background-position: 0% 50%; background-repeat: no-repeat no-repeat;
     27 or
     28 http://perldoc.perl.org/
     29 usually show a <div>. show <input> only at focus.
     30
     31
     32 http://miketaylr.com/pres/html5/forms2.html
     33 http://people.opera.com/brucel/demo/html5-forms-demo.html
     34 http://www.erichynds.com/examples/jquery-multiselect/examples.htm
     35 http://x.wawooo.com/archives/891
     36
     37 http://www.google.com.tw/dictionary
     38 鍵盤選擇時同時改變值
     39 */
     40
     41 if (typeof CeL === 'function')
     42 CeL.setup_module('interact.form.select_input',
     43 {
     44 require : 'interact.DOM.get_node_offset|interact.DOM.parse_URI',
     45 code : function(library_namespace, load_arguments) {
     46
     47 //	requiring
     48 var get_node_offset,parse_URI;
     49 eval(library_namespace.use_function(this));
     50
     51 library_namespace.include_module_resource('select_input.css', this);
     52
     53
     54 /**
     55  * 簡易型 interact.DOM.XML_node @ interact.form.select_input
     56  * @param tag	p.appendChild tag
     57  * @param p	parent node
     58  * @param t	text
     59  * @param classN	className
     60  * @inner
     61  * @ignore
     62  * @return
     63  */
     64 var create_DO = function(tag, p, t, classN) {
     65 	var _e;
     66 	if (t && (typeof t != 'object' || (t instanceof Object)))
     67 		t = document.createTextNode(t + '');
     68 	if (typeof tag == 'object') {
     69 		_e = tag;
     70 	} else if (tag) {
     71 		_e = document.createElement(tag);
     72 		if (classN)
     73 			_e.className = classN;
     74 		if (t)
     75 			_e.appendChild(t);
     76 	} else if (t)
     77 		_e = t;
     78 	if (p && _e)
     79 		p.appendChild(_e);
     80 	return _e;
     81 };
     82
     83
     84 /**
     85  * get scrollbar height
     86  * @return
     87  * @since	2008/9/3 23:31:21
     88  * @inner
     89  * @ignore
     90  * @see
     91  * http://jdsharp.us/jQuery/minute/calculate-scrollbar-width.php
     92  * lazy evaluation
     93  * http://peter.michaux.ca/articles/lazy-function-definition-pattern
     94  */
     95 function scrollbar_width() {
     96 	var _f = scrollbar_width;
     97 	if (!_f.w) {
     98 		var w, p = create_DO('div', document.body), c = create_DO('div', p, ' '), s = p.style;
     99 		s.width = s.height = '80px';
    100 		// 有時沒這行才出得來
    101 		// c.style.width='100%';
    102 		s.overflow = 'hidden';
    103 		w = c.offsetWidth;
    104 		s.overflow = 'scroll';
    105 		_f.w = w - c.offsetWidth;
    106 		// sl('scrollbar_width: '+w+'-'+c.offsetWidth+'='+_f.w);
    107 		document.body.removeChild(p);
    108 	}
    109 	return _f.w;
    110 }
    111
    112
    113 /**
    114  * scroll 到可以看到 object
    115  * TODO:
    116  * 考慮可能沒 scrollbar
    117  * 包括橫向
    118  * @param o	object
    119  * @param [p]	parentNode to scroll
    120  * @return
    121  * @since	2008/9/3 23:31:29
    122  * @inner
    123  * @ignore
    124  */
    125 function scroll_to_show(o, p) {
    126 	if (!p) {
    127 		p = o;
    128 		while ((p = p.parentNode) && p.offsetHeight == p.scrollHeight)
    129 			;
    130 	}
    131 	//sl('scroll_to_show: '+p.scrollTop+', '+p.scrollHeight+', '+p.offsetHeight+', '+o.offsetTop);
    132
    133 	var s, a;
    134 	if (a = o.offsetTop, a < p.scrollTop)
    135 		s = a;
    136 	else if (a = o.offsetTop + o.offsetHeight - p.offsetHeight
    137 			+ scrollbar_width(), a > p.scrollTop)
    138 		if (s = a, a = o.offsetTop, a < s)
    139 			s = a;
    140
    141 	if (!isNaN(s))
    142 		p.scrollTop = s;
    143 }
    144
    145
    146 /*
    147
    148 {
    149 	title: '',
    150 	name: '',
    151 	container: 'id' | obj,
    152 	list: [] | {} | {group1:{}, group2:[],.. },
    153 	default: '' | [],
    154 	type: 'select' | 'radio' | 'checkbox',
    155 }
    156
    157 return <select>
    158
    159
    160 TODO:
    161 複選 <select>
    162 <radio>
    163 <checkbox>
    164 +<label>
    165 autocomplete: 假如所有備取 list 都有一樣的 prefix，則自動完成。
    166 	把後面的用反白自動完成。
    167
    168 在 list 上下安排三角，onmouseover 即可自動滾動。
    169
    170 color panel
    171
    172 http://www.itlearner.com/code/js_ref/choi3.htm
    173 selectName.options[i]=new Options("option_value","option_Text", defaultSelected, selected);
    174 */
    175
    176 /**
    177  * container object, list
    178  * @param o
    179  * @param l
    180  * @return
    181  * @inner
    182  * @ignore
    183  */
    184 function menu_creater(o, l) {
    185
    186 };
    187
    188
    189
    190 //	===================================================
    191
    192 var
    193
    194 //	class private	-----------------------------------
    195
    196
    197 /*	可紀錄的 set class name，不過對大多數人來說，更常用的是 instance.setClassName
    198
    199 usage:
    200 (item[, obj])		set obj to className item, return real className that setted
    201 (0,'prefix')	set prefix & 重設（全部重跑）
    202 */
    203 setClassName=function(i,o,noRec){	//	(0, prefix) or (item, object)
    204  var _t=this,s=_.classNameSet;
    205  if(!_t.settedClass)_t.settedClass=[];
    206
    207  if(!o||typeof o==='object'){
    208   //	設定並回傳 className
    209   //sl('setClassName: test '+'class_'+i+': '+('class_'+i in _t?'<em>YES</em>':'none'));
    210   s=[	i in s?
    211 		s[i].charAt(0)==='~'?s.prefix+s[i].slice(1):
    212 		s[i]:
    213 	''
    214   ];
    215   if('class_'+i in _t)
    216    if(i==='error'||i==='warning')s.unshift(_t['class_'+i]);
    217    else s.push(_t['class_'+i]);
    218   s=s.join(' ');
    219   //sl('setClassName: set '+o+(s?' to ['+s+']':', <em>There is no ['+i+'] in classNameSet or instance set.</em>'));
    220   if(o && (o.className=s,!noRec))
    221    _t.settedClass.push(s,o);
    222   return s;
    223  }
    224
    225  s.prefix=o;
    226  o=_t.settedClass;
    227  //	重設（全部重跑）
    228  for(var i=0;i<o.length;i++)
    229   _f.call(_t,o[0],o[1],1);
    230 },
    231
    232 funcButton=function(_t,t,f,title){	//	add text t, function f to instance _t
    233  var _p=pv(_t),o=create_DO('span',_p.listO,'['),b;
    234  setClassName.call(_t,'functionB',o);
    235  b=create_DO('span',o,t);
    236  setClassName.call(_t,'functionT',b);
    237  b.title=title,b.onclick=f;
    238  create_DO(0,o,']');
    239  return b;
    240 },
    241
    242 //	簡易型
    243 removeAllChild=function(o){
    244  o.innerHTML='';
    245  return o;
    246 },
    247
    248 //	show/hide list
    249 showList=function(show){	//	():get, 0:hide, 1:show
    250  var _t=this,_p=pv(_t),o=_p.listO,s,c=0;
    251
    252  if(!o)return;
    253  s=o.style;
    254  if(show){
    255   c=get_node_offset(_p.inputO);
    256   s.top = c.top + c.height + 2 + 'px';
    257   s.left=c.left+'px';
    258
    259   s.width=c.width+'px';
    260   s.height='';	//	reset
    261   c=s.display='block';
    262   if(_t.maxListHeight&&o.offsetHeight>_t.maxListHeight)
    263    s.height=_t.maxListHeight+'px';
    264  }else if(typeof show!='undefined')
    265   c=s.display='none';
    266
    267  if(c)
    268   create_DO(0,removeAllChild(_p.arrowO),_.textSet[c!='none'?'hideList':'showList']);
    269  else c=s.display;
    270
    271  return c!='none';
    272 },
    273
    274
    275 /*	準備選擇清單的某一項
    276 TODO:
    277 自動完成
    278 到最後若可能自動轉到全部
    279 →
    280 */
    281 cK=0,	//	control key pressed
    282 readyTo=function(e,o){
    283  if(!e)e=event;
    284  var _t=this,_p=pv(_t),c,gI=function(o){
    285   return o&&/*(can_use_special_attribute?o.getAttribute("sIndex"):o.sIndex)*/o.sIndex;
    286  };
    287  //sl('readyTo: '+e.type+', key: '+(e.keyCode||e.which||e.charCode)+', _p.listA: '+(_p.listA&&_p.listA.length));
    288
    289  if(!_p.listA||!_p.listA.length)return;
    290
    291  if(e.type==='mouseover'||e.type==='mouseout'){
    292   if(_p.readyItem)setClassName.call(_t,'item',_p.readyItem,0);
    293   if(e.type==='mouseover')c='item_select',_p.readyItem=o;
    294   else if(c='item',_p.readyItem===o)_p.readyItem=0;
    295  //	需更改 _p.inputO.onkeyup 以防止重新 list!!
    296  }else if(c=e.keyCode||e.which||e.charCode,c==13){
    297   if(_p.readyItem){
    298    //sl('readyTo: 以鍵盤選擇: '+_p.readyItem.innerHTML);
    299    cK=c,_p.readyItem.onclick();	//	用 .click() 無效!
    300    return false;
    301   }else return;
    302  //	key input 用鍵盤控制上下	←↑→↓: 37~40
    303  }else if(c==38||c==40){
    304   cK=c;
    305   o=_p.readyItem;
    306   //sl('readyTo: 以鍵盤移至: '+(o&&(o.getAttribute("sIndex")+','+o.sIndex)));
    307   if(!o)o=_p.listA[c==40?0:_p.listA.length-1];
    308   else{
    309    //if(!o.getAttribute)throw 1;	IE 可用 getAttribute，FF 或許在 appendChild 之後屬性重設?，得用 o.sIndex
    310    c=gI(o)+(c==38?-1:1);
    311    if(c<0||c>=_p.listA.length)return;
    312
    313    setClassName.call(_t,'item',o,0);
    314    o=_p.listA[c];
    315   }
    316   _p.readyItem=o;
    317
    318   scroll_to_show(o,_p.listO);
    319   c='item_select';
    320  }else if(c==35||c==36){	//	35: End, 36: Home
    321   cK=c;
    322   if(o=_p.readyItem)setClassName.call(_t,'item',o,0);
    323   _p.readyItem=o=_p.listA[c==36?0:_p.listA.length-1];
    324   scroll_to_show(o,_p.listO);
    325   c='item_select';
    326  }else if(c==33||c==34){	//	33: PageUP, 34: PageDown
    327   cK=c;
    328   o=_p.readyItem;
    329   if(!o)return;
    330   setClassName.call(_t,'item',o,0);
    331   var i=gI(o),t;
    332   if(c==33){
    333    t=_p.listO.scrollTop-1;
    334    while(i&&_p.listA[i-1].offsetTop>t)i--;
    335   }else{
    336    t=_p.listO.scrollTop+_p.listO.offsetHeight-scrollbar_width();
    337    while(i<_p.listA.length-1&&_p.listA[i+1].offsetTop<t)i++;
    338   }
    339   //sl('readyTo: Page: '+i+', top: '+t+', scroll: '+_p.listO.scrollTop);
    340   if(i==gI(o))
    341    if(c==33){
    342     t-=_p.listO.offsetHeight;
    343     if(t<2)i=0;
    344     else while(i&&_p.listA[i-1].offsetTop>t)i--;
    345    }else{
    346     t+=_p.listO.offsetHeight;
    347     while(i<_p.listA.length-1&&_p.listA[i+1].offsetTop<t)i++;
    348    }
    349   //sl('readyTo: Page: '+i+', top: '+t+', height: '+_p.listO.offsetHeight);
    350   _p.readyItem=o=_p.listA[i];
    351   scroll_to_show(o,_p.listO);
    352   c='item_select';
    353  }else return;
    354
    355  setClassName.call(_t,c,o,0);
    356  return false;
    357 },
    358
    359 //can_use_special_attribute,
    360
    361 //	顯示清單的工具函數
    362 setList=function(l,force,limit,f){
    363  var _t=this,_p=pv(_t),i,c=0,k,o;
    364  if(isNaN(limit))limit=isNaN(_t.maxList)?_.maxList:_t.maxList||Number.MAX_VALUE;
    365  if(!f)f=function(l,i){
    366   var a=_t.onList(l,i),o;
    367   if(!a)return;
    368   o=create_DO('div',0,a[0]);
    369   setClassName.call(_t,'item',o);
    370   o.title=a[1];
    371   k=a[2]||a[1];
    372   o.onmouseover=o.onmouseout=function(e){readyTo.call(_t,e,o);};
    373   o.onclick=function(){var v=_t.onSelect(l,i);_t.setValue(v);_t.onInput(v);};
    374
    375   //	這邊本來放在下面 for 的地方
    376   c++,_p.listO.appendChild(o);
    377   //if(!can_use_special_attribute){o.setAttribute("sIndex",1);can_use_special_attribute=o.getAttribute("sIndex")?1:-1;}
    378   //if(can_use_special_attribute==1)o.setAttribute("sIndex",_p.listA.length);else o.sIndex=_p.listA.length;
    379   o.sIndex=_p.listA.length;//o.setAttribute("sIndex",o.sIndex=_p.listA.length);
    380   _p.listA.push(o);
    381
    382   return o;
    383  };
    384
    385  //_t.showList(0);
    386
    387  _p.listO=removeAllChild(_p.listO),_p.listA=[],_p.readyItem=0;
    388  if(l instanceof Array){
    389   for(i=0;i<l.length&&c<limit;i++)
    390    f(l,i);
    391  }else
    392   for(i in l)
    393    if(c<limit){
    394     f(l,i);
    395    }else break;
    396
    397  //sl('setList: list '+c+' items, key '+k+'=?'+_t.setValue());
    398  if(c==1&&_t.setValue()==k)c=0;	//	僅有一個且與 key 相同
    399  if(!force&&!c)return;	//	無 list
    400
    401  //	add function
    402  if(c!=_t.allListCount)funcButton(_t,_.textSet.allBtn,function(){_t.doFunc=1;_t.focus();_t.setList(_t.setAllList(),1,Number.MAX_VALUE);},'顯示全部 '+_t.allListCount+' 列表。');
    403  if(_t.setValue())funcButton(_t,_.textSet.clearBtn,function(){_t.doFunc=2;_t.focus();_t.onInput(_t.setValue(''));},'清除輸入內容，重新列表。');
    404  funcButton(_t,_.textSet.closeBtn,function(){_t.doFunc=3;_t.showList(0);},'close menu \n關閉列表');
    405
    406  showList.call(_t,1);
    407  return _t.listCount=c;
    408 },
    409
    410 //	return verify 之後的 key(<input>) 值
    411 do_verify=function(k){
    412  var _t=this,c=_t.verify(k||_t.setValue());
    413  //sl('do_verify: input status: '+(c==1?'warning':c==2?'error':'OK'));
    414
    415  if(typeof c==='string')_t.setValue(k=c);	//	可以設定 key 值！
    416  else setClassName.call(_t,c==1?'warning':c==2?'error':'input',pv(_t).inputO,1);
    417
    418  return k;
    419 },
    420
    421 //	簡易設定常用的 onInput 型式
    422 searchInList=function(f,o){	//	o: 傳入 (list, index, key)
    423  var _t=this;
    424  if(typeof f==='string'&&(f in _.searchFunctionSet))f=_.searchFunctionSet[f];
    425
    426  //	因為允許傳入 list，所以不能在這邊用 _t.setAllList() 判別函數，而得要寫一個泛用的。
    427  return _t.onInput=function(k,L,force){
    428   //sl('searchInList, onInput: search ['+k+'] use '+f);
    429
    430   if(!L)L=_t.setAllList();
    431   k=do_verify.call(_t,k||'');
    432
    433   var l,i;
    434
    435   //sl('searchInList: search '+(L instanceof Array?'array':'object'));
    436   if(L instanceof Array){
    437    l=[];//new L.constructor();
    438    for(i=0;i<L.length;i++)
    439     if(o?f(L,i,k):L[i]&&f(L[i],k))l.push(L[i]);	//	search value
    440   }else{
    441    l={};
    442    for(i in L)
    443     if(o?f(L,i,k):i&&f(i,k)||L[i]&&f(L[i],k))l[i]=L[i];	//	search key+value
    444   }
    445   _t.setList(l,force);
    446  };
    447
    448 },
    449
    450
    451 /**
    452  * 切換 [input] / inputted [span]
    453  * @param {Boolean|undefined} to_input	切換至 input or not. default: 切換至 [input]
    454  * @return
    455  * @private
    456  * @inner
    457  * @ignore
    458  */
    459 triggerToInput = function(to_input) {
    460 	var _t = this, _p = pv(_t);
    461 	if (to_input || typeof to_input === 'undefined') {
    462 		//	to <input>
    463 		_p.inputtedO.style.display = 'none';
    464 		if (_t.allListCount)
    465 			_p.arrowO.style.display = 'inline';
    466 		_p.inputO.style.display = 'inline';
    467 		return 1;
    468 	} else {
    469 		//	to inputted <span>
    470 		_t.showList(0);
    471 		_t.setInputted();
    472
    473 		if (to_input = library_namespace.get_style
    474 				&& parseInt(library_namespace.get_style(_p.inputO, 'width'))){
    475 			//library_namespace.debug(to_input);
    476 			//	TODO: +16, +10: magic number
    477 			try {
    478 				//	.get_style(_p.arrowO, 'width') 可能回傳 'auto' @ IE8
    479 				_p.inputtedO.style.width = (to_input+parseInt(library_namespace.get_style(_p.arrowO, 'width'))+16)+'px';
    480 				_p.inputtedO.style.height = (parseInt(library_namespace.get_style(_p.inputO, 'height'))+10)+'px';
    481 			} catch (e) {
    482 				// TODO: handle exception
    483 			}
    484 		}
    485
    486 		_p.arrowO.style.display = _p.inputO.style.display = 'none';
    487 		if(!_p.inputtedO.innerHTML)
    488 			_p.inputtedO.innerHTML=' ';
    489
    490 		//_p.inputtedO.style.border = '3px #aaa dotted';
    491 		_p.inputtedO.style.display = to_input ? 'inline-block' : 'inline';
    492 	}
    493 },
    494
    495 //	TODO: http://blog.xuite.net/sugopili/computerblog/17695447
    496 set_source=function(URL){
    497 	;
    498 },
    499
    500 /*	配置元件
    501
    502 本函數會配置/增加:
    503 <div>		.container
    504 	<input>	.inputO
    505 	<span>	.inputtedO
    506 	<span>	.arrowO
    507 	<div>	.listO
    508
    509 arguments:
    510 <input> 會被當作 inputO 主元件
    511 <select> 會被當作選項
    512 others: container
    513 */
    514 layout=function(o){
    515  var _t=this,_p=pv(_t),t;
    516
    517  if(typeof o!=='object')
    518   o=document.getElementById(o);
    519
    520  if(!o || (o.tagName.toLowerCase() in {hr:1,br:1}))return;	//	** 這邊應該檢查 o 是不是 <hr/> 等不能加 child 的！
    521
    522  //library_namespace.debug(('layout: use <'+o.tagName+(o.id?'#'+o.id:'')+'>: '+o.innerHTML).replace(/</g,'<'));
    523
    524  //	TODO: 這邊應該有一個更完善的刪除策略
    525  if(_t.loaded){
    526   t=_p.container;
    527   //	不必多做功，已經達到所需配置了。
    528   if(t===o.parentNode)return;
    529   for(var i=0,e='inputO,inputtedO,arrowO,listO'.split(',');i<e.length;i++)
    530    //library_namespace.debug('layout: removeChild '+e[i]),
    531    _p[e[i]].parentNode.removeChild(_p[e[i]]);//t.removeChild(_p[e[i]]);
    532   if(!t.childNodes.length)t.parentNode.removeChild(t);
    533  }
    534
    535
    536  //	依照各種不同的傳入 object 作出應對
    537  t=o.tagName.toLowerCase();
    538
    539  if(t==='input'){
    540
    541   try{
    542    //	http://www.w3.org/TR/html5/forms.html#the-pattern-attribute
    543    //	http://www.whatwg.org/specs/web-apps/current-work/#attr-input-pattern
    544    //	http://www.w3school.com.cn/html5/html5_input.asp
    545    t=o.pattern||
    546 	//o.getAttribute&&
    547 	o.getAttribute('pattern');
    548    //	compiled as a JavaScript regular expression with the global, ignoreCase, and multiline flags disabled
    549    //	somewhat as if it implied a ^(?: at the start of the pattern and a )$ at the end
    550    if(t&&(t=new RegExp('^('+t+')?$')))
    551     //library_namespace.debug('set verify pattern of ['+o.id+']: '+t),
    552     _t.set_verify(t);
    553   }catch(e){
    554    library_namespace.err('error pattern: ['+t+']');
    555    library_namespace.err(e);
    556   }
    557
    558   o.parentNode.insertBefore(
    559 	t=_p.container=create_DO('span'),
    560 	_p.inputO=o
    561 	);
    562   setClassName.call(_t,'container',t);
    563
    564   if(!o.className)setClassName.call(_t,'input',o);
    565
    566   t.appendChild(o.parentNode.removeChild(o));
    567
    568   o=t;
    569
    570  }else if(t==='select'){
    571   o.parentNode.insertBefore(t=_p.container=create_DO('span'),o);
    572
    573   _p.inputO=create_DO('input',t);
    574   setClassName.call(_t,'input',_p.inputO);
    575   _p.inputO.name=o.name;
    576   if(o.selectedIndex>=0)_p.inputO.value=o.options[o.selectedIndex].value;
    577
    578   var l={},opt=o.options,i=0;
    579   for(;i<opt.length;i++)
    580    l[opt[i].value||opt[i].innerHTML]=opt[i].innerHTML;
    581
    582   //	list setting
    583   _t.setAllList(l);
    584
    585   o.parentNode.removeChild(removeAllChild(o));
    586
    587   o=t;
    588
    589  }else{
    590   _p.container=o;	//	容器
    591   if(!o.className)setClassName.call(_t,'container',o);
    592
    593   _p.inputO=create_DO('input',o);
    594   setClassName.call(_t,'input',_p.inputO);
    595
    596  }
    597
    598
    599  //	補足其他的設定
    600  _p.inputO.setAttribute("autocomplete","off");
    601
    602  _p.inputtedO=create_DO('span',o);
    603  setClassName.call(_t,'inputted',_p.inputtedO);
    604  _p.inputtedO.style.display='none';
    605
    606  _p.inputtedO.onclick=function(){
    607   _t.clickNow=1;
    608   _t.triggerToInput();
    609   _p.inputO.focus();
    610   _t.clickNow=0;
    611  };
    612
    613  (_p.arrowO=create_DO('span',o,_.textSet.showList))
    614   .title=_.textSet.arrowTitle;
    615  setClassName.call(_t,'arrow',_p.arrowO);
    616
    617  _p.listO=create_DO('div',o);
    618  _p.arrowO.onmouseover=_p.listO.onmouseover=function(){_t.clickNow=1;};
    619  _p.arrowO.onmouseout=_p.listO.onmouseout=function(){_t.clickNow=0;};
    620  setClassName.call(_t,'list',_p.listO);
    621  _t.showList(0);
    622
    623
    624  // event setting
    625  //_p.inputO.onmouseover=
    626  _p.inputO.onkeydown=function(e){readyTo.call(_t,e);};
    627  _p.inputO.onmouseup=_p.inputO.onkeyup=_p.inputO.ondragend=function(e){
    628   if(!e)e=event;
    629   var c=e.keyCode||e.which||e.charCode;
    630   //sl('up: '+e.type+', key: '+c+', _p.listA: '+(_p.listA&&_p.listA.length));
    631   if(cK&&cK==c){cK=0;return false;}
    632   //	Esc
    633   if(c==27){_t.showList(0);return false;}
    634   _t.clickNow=1;_t.onInput(_t.setValue());
    635  };
    636  _p.inputO.onmouseout=function(){_t.clickNow=0;};
    637  if(_p.inputO.addEventListener)_p.inputO.addEventListener('dragdrop',_p.inputO.ondragend,false);
    638  //if(window.addEventListener)window.addEventListener('click',function(){_t.showList(0);},true);
    639  //addListener(0,'click',function(){sl('close..');_t.showList(0);sl('close done.');})
    640  _p.inputO.onblur=function(){
    641   //if(_t.verify(_t.setValue())==2){alert('Wrong input!');return false;}	//	這在 Firefox 似乎沒啥效果..
    642 /*	設定這項在按 _p.arrowO 的時候會出問題，所以建議在其他地方自訂。
    643   if(_t.setValue() && (_t.setValue() in _t.setAllList()))
    644    _t.triggerToInput(0);
    645 */
    646   //	TODO: 假如以鍵盤離開，應該也 showList(0);
    647   //library_namespace.debug('clickNow='+_t.clickNow,1,'_p.inputO.onblur');
    648   if(_t.clickNow)_t.clickNow=0;
    649   else _t.showList(0);
    650  };
    651
    652  //	show/hide by press arrow
    653  _p.arrowO.onclick=function(){
    654   //sl('arrowO.onclick start');
    655   _t.clickNow=1;
    656   if(_t.showList())
    657    //	正在顯示就把他關起來
    658    _t.showList(0);
    659   else
    660    //	沒在顯示就把他開起來: setValue 設定完 list，onInput 模擬 key-down
    661    _t.onInput(_t.setValue(),0,1);
    662   _t.clickNow=0;
    663   //sl('arrowO.onclick end');
    664  };
    665  // ondblclick: double click
    666  //_p.inputO.ondblclick=function(){_t.onInput(_p.inputO.value,0,1);};
    667
    668
    669  _t.loaded=1;	//	isLoaded
    670
    671 },
    672
    673 //	instance constructor	---------------------------
    674 instanceL=[],
    675 initI=function(o,l,s){	//	(HTML object, list: Array or Object)
    676  var _t=this,_p;
    677  // objects setting
    678  if(typeof o!='object')
    679   //library_namespace.debug('Use object ['+o+']'),
    680   o=document.getElementById(o);
    681
    682  _p=pv(_t);	//	also do initial
    683  instanceL.push(_t);	//	for destructor
    684
    685  if(o)
    686 	layout.call(this,o);
    687 /*
    688  else{
    689   //throw new Error(1,'Can not get document object'+(o?' ['+o+']':'')+'!');
    690   return;
    691  }
    692 */
    693
    694  //	list setting
    695  if(l&&!_t.allListCount)_t.setAllList(l);
    696
    697  if(_p.arrowO)
    698   _p.arrowO.style.display=_t.allListCount?'inline':'none';	//	無 list 的話先不顯示，等有 list 再說。
    699
    700  //	setup default inputted value
    701  _t.dInputted=_t.setValue;
    702
    703  if(s)
    704   _t.setSearch(s);
    705  //return _t;
    706 };
    707
    708
    709 //===================================================
    710
    711 /*
    712
    713 _=this
    714
    715 TODO:
    716 浮水印 background-image:url();
    717
    718
    719 HISTORY:
    720 2008/7/22 0:38:14	create
    721 7/27 22:55:18	verify()
    722 8/7 21:18:47	attach()
    723 */
    724 /**
    725 * 提供有選單的  input
    726 * @class	form 的 functions
    727 * @see
    728 * http://dojocampus.org/explorer/#Dijit_Form%20Controls_Filtering%20Select_Basic
    729 */
    730 CeL.interact.form.select_input
    731 =function(){initI.apply(this,arguments);load_arguments&&load_arguments.apply(this,arguments);},
    732
    733 //	(instance private handle)	不要 instance private 的把這函數刪掉即可。
    734 _p='_'+(Math.random()+'').replace(/\./,''),
    735 //	get private variables (instance[,destroy]), init private variables (instance[,access function list[, instance destructor]])
    736 pv=function(i,d,k){var V,K=_p('k');return arguments.callee.caller===_p('i')?(V=_p(i[K]=_p()),V.O=i,V.L={}):(K in i)&&(V=_p(i[K]))&&i===V.O?d?(_p(i[K],1),delete i[K]):V.L:{};};
    737
    738 //	(for inherit)	不要 inherit 的把這段刪掉即可。
    739 //(_.clone=arguments.callee).toString=function(){return '[class_template]';};
    740
    741
    742 //	class destructor	---------------------------
    743 /*
    744 please call at last (e.g., window.unload)
    745
    746 usage:
    747 classT=classT.destroy();
    748 or if you has something more to do:
    749 classT.destroy()&&classT=null;
    750 */
    751
    752 _.destroy=function(){for(var i=0;i<instanceL.length;i++)instanceL[i].destroy();_p();};
    753
    754 //	(instance private handle, continue)
    755 eval('_p=(function(){var '+_p+'={a:pv,d:_.destroy,c:0,k:"+pv+'+Math.random()+'",i:initI};return function(i,d){var f=arguments.callee.caller;if(f==='+_p+'.a){if(!d)return i in '+_p+'?'+_p+'[i]:('+_p+'[i='+_p+'.c++]={},i);'+_p+'[i]={};}if(f==='+_p+'.d)'+_p+'={};}})();');
    756 _p.toString=function(){return'';};
    757
    758
    759 /*
    760 //	測試是否可用自訂之屬性
    761 var o=document.createElement('div');
    762 o.setAttribute('testA',2);
    763 can_use_special_attribute=o.getAttribute('testA');
    764 sl('can_use_special_attribute: '+can_use_special_attribute);
    765 */
    766
    767 //	class public interface	---------------------------
    768
    769
    770 //	預設清單最大顯示數
    771 _.maxList=10;
    772
    773
    774 //	searchInList 常用到的函數
    775 _.searchFunctionSet = {
    776 	allTheSame : function(i, k) {
    777 		return (i + '') === k;
    778 	},
    779 	startWith : function(i, k) {
    780 		return (i + '').slice(0, k.length) === k;
    781 	},
    782 	//	不管大小寫 Whether the case
    783 	startWithWC : function(i, k) {
    784 		return (i + '').slice(0, k.length).toLowerCase() === k.toLowerCase();
    785 	},
    786 	includeKey : function(i, k) {
    787 		return (i + '').toLowerCase().indexOf(k.toLowerCase()) !== -1;
    788 	},
    789 	includeKeyWC : function(i, k) {
    790 		return (i + '').toLowerCase().indexOf((k + '').toLowerCase()) !== -1;
    791 	},
    792 	always : function() {
    793 		return true;
    794 	}
    795 };
    796
    797
    798 //	預設 className	前有 ~ 的會轉成 prefix
    799 _.classNameSet={
    800  prefix:'si_',
    801  container:'~container',
    802  input:'~input',
    803  inputted:'~inputted',
    804  arrow:'~arrow',
    805  list:'~list',
    806  item:'~item',
    807  item_select:'~item_select',
    808  functionB:'~function',
    809
    810  functionT:'~functionText',
    811  error:'~error',
    812  warning:'~warning'
    813 };
    814
    815
    816 //	預設顯示文字
    817 _.textSet={
    818  showList:'▼',	//	4 way: [▴▸▾◂]
    819  hideList:'▲',
    820  arrowTitle:'trigger list \n切換顯示查詢列表',
    821
    822  allBtn:'全部',
    823  clearBtn:'清除',
    824  closeBtn:'關閉'//'×'
    825 };
    826
    827
    828 //	default 欄位驗證 pattern
    829 //	http://blog.wu-boy.com/2009/06/16/1454/
    830 //	TODO: ID, Age, 電話, 地址, 性別, ..
    831 _.default_verify_pattern = {
    832 		'|word' : /^\w*$/,
    833 		'word' : /^\w+$/,
    834 		//	整數
    835 		'|integer' : /^[+-]?\d*$/,
    836 		'integer' : /^[+-]?\d+$/,
    837 		//	自然數
    838 		'|natural' : /^([1-9]\d*)?$/,
    839 		'natural' : /^[1-9]\d*$/,
    840 		//	十進位小數
    841 		'|decimal' : /^\d*(\.\d+)?$/,
    842 		'decimal' : /^(\d+|\d*\.\d+)$/,
    843 		//	數字
    844 		'|digit' : /^\d*$/,
    845 		'digit' : /^\d$/,
    846
    847 		IPv4 : /^[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}\.[12]?\d{1,2}$/,
    848
    849 		URI : function(k){return !!parse_URI(k);},
    850
    851 		//	RFC 2822
    852 		//	http://regexlib.com/DisplayPatterns.aspx
    853 		//'RFC2822' : /^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/,
    854 		//	http://www.regular-expressions.info/email.html
    855 		//'email' : /^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+([a-z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\b$/i,
    856 		email : /^[a-z0-9+_~-]+(\.[a-z0-9+_~-]+)*@([a-z\d]([a-z\d-]*[a-z\d])?\.)+([a-z]{2}|com|org|net)\b$/i,
    857
    858 		any : function(k){return k===''?2:0;}
    859 };
    860
    861 _.prototype={
    862 //	應該盡量把東西放在 class，instance少一點？
    863
    864 //	instance public interface	-------------------
    865
    866
    867 /*	click 事件進行中
    868 TODO:
    869 用更好的方法取代
    870 */
    871 clickNow:0,
    872
    873 //	instance 的 <input>,.. 之 className, override _.classNameSet.input,..
    874 //class_input:'~',
    875 //class_item:'~',
    876 //..
    877
    878 //maxList:\d,
    879
    880 //	預設清單 height (px)
    881 maxListHeight:200,
    882
    883
    884 //	設定/取得所有可選的 list
    885 setAllList:function(l){
    886  var _t=this,_p=pv(_t),i,c=0,s=_p.arrowO;
    887  _t.showList(0);
    888  if(typeof l==='object'){
    889   _p.list=l;
    890   if(l instanceof Array)c=_t.allListCount=l.length;	//	這不準，得用 onList 測試。
    891   else{for(i in l)c++;_t.allListCount=c;}
    892   //sl('setAllList: Get about '+_t.allListCount+' items.');
    893   if(s)
    894    if(s=s.style,!c)s.display='none';
    895    else if(_t.autoShowArrow)s.display='';
    896  }
    897  return _p.list;
    898 },
    899 //	自動於有 list 時 show arrow，無時 hide
    900 autoShowArrow:0,
    901
    902 //	設定要顯現的 list，會回傳 list，需注意可能被更改！
    903 setList:function(l){	//	key
    904  return setList.apply(this,arguments);
    905 },
    906
    907 showList:function(show){
    908  return showList.apply(this,arguments);
    909 },
    910
    911 /*
    912 showArrow:function(show){
    913  var a=pv(this).arrowO.style;
    914  if(typeof show!='undefined')a.display=show?'':'none';
    915  return a.display;
    916 },
    917 */
    918
    919 //	每次 input 就會被 call 一次。可用 instance.setSearch('includeKey') 簡易設定
    920 onInput:function(k){	//	key
    921 },
    922
    923 //	設定表單文字欄位的欄位驗證	return 1: warning, 2: error ('Suffering from a pattern mismatch'), string: 將輸入改為回傳值, else OK
    924 //	另外可設定 onkeypress(){return true/false;} 來對每一次按鍵作 check。但這不能處理 paste。	http://irw.ncut.edu.tw/peterju/jscript.html#skill
    925 verify:function(k){	//	key
    926 },
    927
    928 set_verify:function(v){
    929 	var m=_.default_verify_pattern;
    930 	if(library_namespace.is_Object(m)&&(v in m))
    931 		v = m[v];
    932
    933 	if(v instanceof RegExp)
    934 		this.verify = function(k) {
    935 			return v.test(k) ? 0 : 2;
    936 		};
    937 	else if(typeof v === 'function')
    938 		this.verify = v;
    939 	else if(typeof v === 'string' && (m=v.match(/^(\d*)-(\d*)$/))&&(m[1]||m[2]))
    940 		this.verify = new Function('k','return isNaN(k)'+(m[1]?'||k<'+m[1]:'')+(m[2]?'||k>'+m[2]:'')+' ? 2 : 0;');
    941 	else
    942 		library_namespace.err('error verify condition of ['+pv(this).inputO.id+']: ['+v+']');
    943
    944 	return this;
    945 },
    946
    947 //	input: (list, index), return [value, title[, key=title||value]]
    948 onList:function(l,i){
    949  return [l[i]||i,l instanceof Array?l[i]:i];
    950 },
    951
    952 //	input: (list, index), return value to set as input key
    953 onSelect:function(l,i){
    954  return l instanceof Array?l[i]:i;
    955 },
    956
    957 /*	searchInList 的減縮版
    958 _.searchInList.call(_instance_,'includeKey');
    959 eq
    960 _instance_.setSearch('includeKey');
    961 */
    962 setSearch:function(f){
    963  return searchInList.call(this,f);
    964 },
    965
    966 setClassName:function(n){
    967  var t=this;
    968  if(n)t.class_input=t.class_error=t.class_warning=n;
    969  else if(typeof n!='undefined'){delete t.class_input;delete t.class_error;delete t.class_warning;}
    970  return setClassName.call(this,'input',pv(this).inputO);
    971 },
    972
    973
    974 setProperty:function(p,v){
    975  var i=pv(this).inputO;
    976  //sl('setProperty: '+p+'='+i[p]+'→'+v);
    977  if(typeof v!='undefined'&&v!=null)i[p]=v;
    978  return i[p];
    979 },
    980
    981 //	set/get input value
    982 setValue:function(v){
    983  if(typeof v!=='undefined')
    984   this.triggerToInput();
    985  //library_namespace.log('setValue: '+this.setProperty('value',v));
    986  v=this.setProperty('value',v);
    987  //library_namespace.log('setValue: '+v);
    988  //	TODO: do not use arguments
    989  if(arguments.callee.caller!==do_verify)
    990   //library_namespace.log('setValue: call do_verify('+v+'), list: ['+this.allListCount+']'+this.setAllList()),
    991   do_verify.call(this,v);
    992  return v;
    993 },
    994
    995 //	set inputted value: 轉換成輸入過的 <span> 時，設定其之值。
    996 setInputted:function(v){
    997  var _p=pv(this),i=_p.inputO;
    998  if(typeof v==='undefined')v=this.dInputted();	//	dInputted: default inputted value, =setValue
    999  create_DO(0,removeAllChild(_p.inputtedO),v);
    1000  return v;
    1001 },
    1002
    1003 setMaxLength:function(l){
    1004  //sl('setMaxLength: set length '+(l>0?l:null));
    1005  return this.setProperty('maxLength',l>0?l:null);
    1006 },
    1007
    1008 setName:function(n){
    1009  this.setProperty('id',n);
    1010  return this.setProperty('name',n);
    1011 },
    1012
    1013 setTitle:function(t){
    1014  if(t)pv(this).inputtedO.title=t;
    1015  return this.setProperty('title',t||null);
    1016 },
    1017
    1018 //	切換 inputted span/input
    1019 triggerToInput:function(){
    1020  return triggerToInput.apply(this,arguments);
    1021 },
    1022
    1023
    1024 /*
    1025 	for Unobtrusive JavaScript: 為未啟用JavaScript的情況提供替代方案。
    1026 	接上 <input> 或 <select>
    1027 */
    1028 attach:function(o){	//	(input or select object)
    1029  //sl('attach: '+o);
    1030  //o.replaceNode(_p.inputO);
    1031  o=layout.call(this,o);
    1032  this.setAllList(this.setAllList());
    1033  return o;
    1034 },
    1035
    1036
    1037 //	(focus or blur, 不驅動 onfocus/onblur)
    1038 focus:function(f){	//	,noE
    1039  var i=pv(this).inputO;
    1040 /*
    1041  sl('focus: '+(f?'focus':'blur')+(noE?' and no event':''));
    1042  if(f||typeof f==='undefined'){
    1043   if(noE)noE=i.onfocus,i.onfocus=null;
    1044   i.focus();
    1045   //if(noE)i.onfocus=noE;
    1046  }else{
    1047   if(noE)noE=i.onblur,i.onblur=null;else this.showList(0);
    1048   i.blur();
    1049   //if(noE)i.onblur=noE;
    1050  }
    1051 */
    1052  if(f||typeof f==='undefined')
    1053   try{
    1054    //	@IE5-8 initial:  Error @CeL: 2110 [Error] (facility code 10): 控制項不可見、未啟動或無法接受焦點，因此無法將焦點移到控制項上。
    1055    //	Error @CeL: 2110 [Error] (facility code 10): Can't move focus to the control because it is invisible, not enabled, or of a type that does not accept the focus.
    1056    i.focus();
    1057   }catch(e){}
    1058  else
    1059   this.showList(0),
    1060   i.blur();
    1061 },
    1062
    1063
    1064 //	instance destructor	---------------------------
    1065 /*
    1066 usage:
    1067 instance=instance.destroy();
    1068 or if you has something more to do:
    1069 instance.destroy()&&instance=null;
    1070 */
    1071 destroy : function(){
    1072 	pv(this, 1);
    1073 }
    1074
    1075 };	//	_.prototype=
    1076
    1077
    1078 //	===================================================
    1079
    1080
    1081 //	prevent re-use. 防止再造
    1082 //delete _.clone;
    1083
    1084 _.allow_inherit = true;
    1085
    1086
    1087
    1088 return (
    1089 	CeL.interact.form.select_input
    1090 );
    1091 }
    1092
    1093
    1094 });
    1095
    1096