有许多很有趣的自然语言界面,在这个插件中,我们想要使用自定义的表单元素来定制一个自然语言界面。
HTML结构
html结构实用一个form表单,里面包含一些select元素和input元素。
<form id="nl-form" class="nl-form"> I feel like eating <select> <option value="1" selected>any food</option> <option value="2">Indian</option> <option value="3">French</option> <option value="4">Japanese</option> <option value="2">Italian</option> </select> <br />in a <select> <option value="1" selected>standard</option> <option value="2">fancy</option> <option value="3">hip</option> <option value="4">traditional</option> <option value="2">fine</option> </select> restaurant <select> <option value="1" selected>anytime</option> <option value="1">at 7 p.m.</option> <option value="2">at 8 p.m.</option> <option value="3">at 9 p.m.</option> </select> in <input type="text" value="" placeholder="any city" data-subline="For example: <em>Los Angeles</em> or <em>New York</em>"/> <div class="nl-submit-wrap"> <button class="nl-submit" type="submit">Find a restaurant</button> </div> <div class="nl-overlay"></div> </form>
我们希望将select 元素转换为一个自定义的下拉列表,如下结构:
<div class="nl-field nl-dd"> <a class="nl-field-toggle">any food</a> <ul> <li class="nl-dd-checked">any food</li> <li>Indian</li> <li>French</li> <li>Japanese</li> <li>Italian</li> </ul> </div>
切换时句子中视觉效果的一部分,当我们点击了列表中的某一项时,我们隐藏列表,使该选项显示在句子的当前位置上。
input元素也要被转换为一个类似的元素:
<div class="nl-field nl-ti-text"> <a class="nl-field-toggle">any city</a> <ul> <li class="nl-ti-input"> <input type="text" value="" placeholder="any city" /> <button class="nl-field-go">Go</button> </li> <li class="nl-ti-example">For example: <em>Los Angeles</em> or <em>New York</em></li> </ul> </div>
JAVASCRIPT
首先定义一个代表表单的对象:
function NLForm( el ) { // the form element this.el = el; // the overlay this.overlay = this.el.querySelector( '.nl-overlay' ); // array with all the possible custom fields this.fields = []; // counter for each custom field this.fldOpen = -1; this._init(); }
然后参加一些结构来替换原来表单的html结构。我们将定义一个代表各个自动的对象-NLField。
NLForm.prototype = { _init : function() { var self = this; Array.prototype.slice.call( this.el.querySelectorAll( 'select' ) ).forEach( function( el, i ) { self.fldOpen++; self.fields.push( new NLField( self, el, 'dropdown', self.fldOpen ) ); } ); Array.prototype.slice.call( this.el.querySelectorAll( 'input' ) ).forEach( function( el, i ) { self.fldOpen++; self.fields.push( new NLField( self, el, 'input', self.fldOpen ) ); } ); }, ... } function NLField( form, el, type, idx ) { this.form = form; // the original HTML element this.elOriginal = el; this.pos = idx; this.type = type; this._create(); this._initEvents(); } NLField.prototype = { _create : function() { if( this.type === 'dropdown' ) { this._createDropDown(); } else if( this.type === 'input' ) { this._createInput(); } }, ... }
结构的不同依赖与它是一个select选择框或是一个input输入框。
NLField.prototype = { ... _createDropDown : function() { var self = this; this.fld = document.createElement( 'div' ); this.fld.className = 'nl-field nl-dd'; this.toggle = document.createElement( 'a' ); this.toggle.innerHTML = this.elOriginal.options[ this.elOriginal.selectedIndex ].innerHTML; this.toggle.className = 'nl-field-toggle'; this.optionsList = document.createElement( 'ul' ); var ihtml = ''; Array.prototype.slice.call( this.elOriginal.querySelectorAll( 'option' ) ).forEach( function( el, i ) { ihtml += self.elOriginal.selectedIndex === i ? ' ' + el.innerHTML + ' ' : ' ' + el.innerHTML + ' '; // selected index value if( self.elOriginal.selectedIndex === i ) { self.selectedIdx = i; } } ); this.optionsList.innerHTML = ihtml; this.fld.appendChild( this.toggle ); this.fld.appendChild( this.optionsList ); this.elOriginal.parentNode.insertBefore( this.fld, this.elOriginal ); this.elOriginal.style.display = 'none'; }, _createInput : function() { var self = this; this.fld = document.createElement( 'div' ); this.fld.className = 'nl-field nl-ti-text'; this.toggle = document.createElement( 'a' ); this.toggle.innerHTML = this.elOriginal.placeholder; this.toggle.className = 'nl-field-toggle'; this.optionsList = document.createElement( 'ul' ); this.getinput = document.createElement( 'input' ); this.getinput.setAttribute( 'type', 'text' ); this.getinput.placeholder = this.elOriginal.placeholder; this.getinputWrapper = document.createElement( 'li' ); this.getinputWrapper.className = 'nl-ti-input'; this.inputsubmit = document.createElement( 'button' ); this.inputsubmit.className = 'nl-field-go'; this.inputsubmit.innerHTML = 'Go'; this.getinputWrapper.appendChild( this.getinput ); this.getinputWrapper.appendChild( this.inputsubmit ); this.example = document.createElement( 'li' ); this.example.className = 'nl-ti-example'; this.example.innerHTML = this.elOriginal.getAttribute( 'data-subline' ); this.optionsList.appendChild( this.getinputWrapper ); this.optionsList.appendChild( this.example ); this.fld.appendChild( this.toggle ); this.fld.appendChild( this.optionsList ); this.elOriginal.parentNode.insertBefore( this.fld, this.elOriginal ); this.elOriginal.style.display = 'none'; }, ... }
最后,我们在字段上绑定一些事件。对于下拉列表框,我们将使用用户选择的选项来更新表单的内容,对于输入框,根据用户输入的内容来更新表单的内容。
NLField.prototype = { ... _initEvents : function() { var self = this; this.toggle.addEventListener( 'click', function( ev ) { ev.preventDefault(); ev.stopPropagation(); self._open(); } ); this.toggle.addEventListener( 'touchstart', function( ev ) { ev.preventDefault(); ev.stopPropagation(); self._open(); } ); if( this.type === 'dropdown' ) { var opts = Array.prototype.slice.call( this.optionsList.querySelectorAll( 'li' ) ); opts.forEach( function( el, i ) { el.addEventListener( 'click', function( ev ) { ev.preventDefault(); self.close( el, opts.indexOf( el ) ); } ); el.addEventListener( 'touchstart', function( ev ) { ev.preventDefault(); self.close( el, opts.indexOf( el ) ); } ); } ); } else if( this.type === 'input' ) { this.getinput.addEventListener( 'keydown', function( ev ) { if ( ev.keyCode == 13 ) { self.close(); } } ); this.inputsubmit.addEventListener( 'click', function( ev ) { ev.preventDefault(); self.close(); } ); this.inputsubmit.addEventListener( 'touchstart', function( ev ) { ev.preventDefault(); self.close(); } ); } }, _open : function() { if( this.open ) { return false; } this.open = true; this.form.fldOpen = this.pos; var self = this; this.fld.className += ' nl-field-open'; }, close : function( opt, idx ) { if( !this.open ) { return false; } this.open = false; this.form.fldOpen = -1; this.fld.className = this.fld.className.replace(/\b nl-field-open\b/,''); if( this.type === 'dropdown' ) { if( opt ) { // remove class nl-dd-checked from previous option var selectedopt = this.optionsList.children[ this.selectedIdx ]; selectedopt.className = ''; opt.className = 'nl-dd-checked'; this.toggle.innerHTML = opt.innerHTML; // update selected index value this.selectedIdx = idx; // update original select element´s value this.elOriginal.value = this.elOriginal.children[ this.selectedIdx ].value; } } else if( this.type === 'input' ) { this.getinput.blur(); this.toggle.innerHTML = this.getinput.value.trim() !== '' ? this.getinput.value : this.getinput.placeholder; this.elOriginal.value = this.getinput.value; } } ... }