有许多很有趣的自然语言界面,在这个插件中,我们想要使用自定义的表单元素来定制一个自然语言界面。
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;
}
}
...
}