SMMS有个建表工具,尝试使用HTML模仿出一个简单的做为练习.
截图:
一.实现SMMS建表工具的操作
- 点击单元格,进入编辑状态.
- 按TAB切换单元格
- 按方向箭切换单元格
- 按空格设定取消主键列
二.实现思路:
1.DOM结构使用div(行)span(列) <div><span></span><span></span>...</div>
2.每个span上设置tabindex=0,当获得焦点时,在其内生成表单元素.input select textarea等等.失去焦点时,将值取出来写到span内.
3.给input设定onkeydown事件,方向键切换临近单元格
三.实现过程中:
在实现TAB时费了不少功夫,按tab时,总是不能按预想的那样移到前一个或者后一个列上.在向span中加入了表单元素后,就更加混乱.后来发现在每个span上tabindex=0这个属性,由于值一样,所以tab就按相临的去找.
当span得到焦点后,会向其内生成一个表单元素.表单元素上绑定失去焦点事件blue和keydown事件.blue用于将表单元素的值取出,然后写到span中.这里有个细节就是,span得到焦点后,要将tabindex=-1,即按tab时,不能得到焦点.如果不这样做,那么span处于编辑状态后,按tab就移动不了.因为它会移动到span上,而span的得到焦点事件又是在其内生成表单.这样就形成一个死循环.所以当span得到焦点后,需要将tabindex=-1,然后在该 span内的表单元素的失去焦点事件上再tabindex=0.这样就实现了焦点顺序的正确.不会因为新生成了表单元素而乱(表单元素默认能获得焦点).
按上下方向键盘切换到上下行的同列,这个较简单,取得当前事件span的列索引,然后找到上下行的同列span让它获得焦点.注意是否有上下行.
按左右方向键切换到同行临近列.如果是输入状态的文本框则判断光标位置,如果在最左并且按左方向,则移动.同理按右方向键也一样.如果是select框,则直接移动到临近.取消默认行为.textarea没做此功能.
四.主要代码
.field-table-box {border-top: 1px solid #ccc;border-left: 1px solid #ccc; } .field-rows{ height:300px;overflow-y:auto; } .field-form-control {border: none;width: 100%;height: 100%;outline:none;/*用谷歌浏览器时:如果元素得到焦点.会默认有个蓝色边框.加这个可以去掉.(EDGE,FF没这问题)*/ }.field-form-control:focus {background-color: #75e0f5;border:none;}.field-col-title, .field-col {display: inline-block;font-size: 14px;height: 24px;line-height: 24px;text-align: center;vertical-align: top;border-right: 1px solid #ccc;border-bottom: 1px solid #ccc;outline:none; }.field-col {color: #444; } .field-col.editinfo {height: 200px;line-height: 200px; }.field-col.pk {cursor: crosshair;color: orangered;}.field-col-title.pk, .field-col.pk {width: 40px;}.field-col-title.name, .field-col.name {width: 140px;}.field-col-title.type, .field-col.type {width: 140px;}.field-col-title.len, .field-col.len, .field-col-title.len2, .field-col.len2 {width: 60px;}.field-col-title.dval, .field-col.dval {width: 120px;}.field-col-title.info, .field-col.info {width: 180px;}.field-col.info{} .field-form-control.info {width: 100%; }.field-col-title.delrow, .field-col-title.moverow, .field-col-title.moverow, .field-col-title.insertrow {width: 40px; }.field-col.delrow, .field-col.moverow-down, .field-col.moverow-up, .field-col.insertrow {font-size: 0;width: 40px;cursor: pointer; }.field-col.delrow:hover, .field-col.moverow-down:hover, .field-col.moverow-up:hover, .field-col.insertrow:hover {font-size: 16px; font-weight: 600;}.field-col.delrow {color: orangered; } .field-col.delrow:active{color: #fff;background-color: orangered; } .field-col.moverow-up, .field-col.moverow-down {color: #999; }.field-col.moverow-up:active {color: #fff;background-color: blue;}.field-col.moverow-down:active {color: #fff;background-color: green;}.field-col.insertrow {color: #4679ca; }.field-col.insertrow:active {color: #fff;background-color: #000;}
<div class="field-table-box" id="field_table_box"><div class="field-table-cols"><h5 class="field-col-title pk">PK</h5><h5 class="field-col-title name">列名</h5><h5 class="field-col-title type">数据类型</h5><h5 class="field-col-title len">长度</h5><h5 class="field-col-title len2">精度</h5><h5 class="field-col-title dval">默认值</h5><h5 class="field-col-title info">列说明</h5><h5 class="field-col-title moverow">上移</h5><h5 class="field-col-title moverow">下移</h5><h5 class="field-col-title insertrow">插行</h5><h5 class="field-col-title delrow">删</h5></div> </div><template id="tpl_fieldrow"><div class="fieldrow"><span class="field-col pk noselect" onclick="onClick_SetPk(this)" onkeyup="onkeyup_SetPk(event,this)" tabindex="0"></span><span class="field-col name" onfocus="fieldCol_focus_toEdit(this)" tabindex="0"></span><span class="field-col type" onfocus="fieldColType_toSelect(this)" tabindex="0" val="1">字符串</span><span class="field-col len" onfocus="fieldCol_focus_toEdit(this)" tabindex="0">20</span><span class="field-col len2" onfocus="fieldCol_focus_toEdit(this)" tabindex="0">2</span><span class="field-col dval" onfocus="fieldCol_focus_toEdit(this)" tabindex="0">NULL</span><span class="field-col info" onfocus="fieldCol_focus_info(this)" tabindex="0"></span><span class="field-col moverow-up noselect" onclick="fieldColOp_click_moveRow(this)">▲</span><span class="field-col moverow-down noselect" onclick="fieldColOp_click_moveRow(this)">▼</span><span class="field-col insertrow noselect" onclick="fieldColOp_click_insertRow(this)">☨</span><span class="field-col delrow noselect" onclick="fieldCopOp_click_delRow(this)">✕</span></div> </template> <template id="tpl_fieldcol_type_select"><select class="field-form-control" onblur="fieldType_select_blur(this)" onkeydown="fieldType_select_keydown(event,this)"><option value="1">字符串</option><option value="2">整数</option><option value="3">小数</option><option value="4">时间</option></select> </template>
/* 关于列的编辑功能模仿了SMMS工具的建表功能 例如:点击某一个列名,它就会变成可编辑的,实现上是在点击后,套了一个INPUT框 */ $(function () {refreshEditStatus(); }) // 编辑列之后刷新编辑区域状态 function refreshEditStatus() {// 自动增加新的编辑行 addRowTpl();// 说明textarea框或处于编辑时的放大状态,此处还原$('.field-col').removeClass('editinfo'); } // 增加一行到制表工具中:如果没新行加入(列名为空视为有新行),否则不加入 function addRowTpl() {var hasNewRow = false;$('#field_table_box').find('.field-col.name').each(function (){if (String.IsNullOrWhiteSpace($(this).html())){hasNewRow = true;return false;}})if (hasNewRow == false)$('#field_table_box').append($('#tpl_fieldrow').html()); } // 主键列: 按空格时,按上下键时 function onkeyup_SetPk(event, thisobj) {var e = event || window.event;//console.log(e.keyCode);if (e.keyCode == 32){onClick_SetPk(thisobj);return;}// 当前编辑对象在fieldrow中的索引位置var colindex = $(thisobj).index();if (e.keyCode == 38){// 上一行var prevrow = $(thisobj).parent('.fieldrow').prev();if (prevrow.length == 1){prevrow.children().eq(colindex).focus();}} else if (e.keyCode == 39){// 移向右一列 不考虑右侧无列情况 $(thisobj).next().focus();}else if (e.keyCode == 40){// 下一行var nextrow = $(thisobj).parent('.fieldrow').next();if (nextrow.length == 1){nextrow.children().eq(colindex).focus();}} } // 切换设置 鼠标单击时切换 function onClick_SetPk(thisobj) {if ($(thisobj).html() == ''){$('.field-col.pk').html('');$(thisobj).html('主键');} else{$('.field-col.pk').html('');}return; } // 列名称,长度,默认值: 获得焦点后,其内生成input function fieldCol_focus_toEdit(thisobj) {if ($(thisobj).find('input').length == 1)return;var html = '<input class="field-form-control" οnblur="field_input_blur(this)" οnkeydοwn="field_input_updownArrow(event,this)" type="text" />';var val = $(thisobj).html();$(thisobj).html(html).find('input').focus().val(val);$(thisobj).prop('tabindex', '-1'); } // // 列名称,长度,默认值INPUT框失去焦点事件:框去掉,值写到父级SPAN中 function field_input_blur(thisobj) {var parent = $(thisobj).parent('.field-col');var val = $(thisobj).val();//console.log(val);parent.html(val).prop('tabindex', '0');// refreshEditStatus();// } // // 列名称,长度,默认值INPUT框支持上下方向键盘切换到上下行同一列 function field_input_updownArrow(event, thisobj) {var e = event || window.event;// 当前编辑对象在其行内的列索引位置var colindex = $(thisobj).parent().index();if (e.keyCode == 38){// 上一行var prevrow = $(thisobj).parent().parent('.fieldrow').prev();if (prevrow.length == 1){prevrow.children().eq(colindex).focus();}} else if (e.keyCode == 40){// 下一行var nextrow = $(thisobj).parent().parent('.fieldrow').next();if (nextrow.length == 1){nextrow.children().eq(colindex).focus();}}// 按左右键时,如果光标处在文本最右,再按右,则移到后列.如果在最左,再按左,则移到前列// 光标焦点位置.如果=文本长度则在最后,为0则在最前//console.log($(thisobj).prop('selectionEnd'));var position = $(thisobj).prop('selectionEnd');if (e.keyCode == 37){// 左移动 未考虑左边没有列的情况,因为第1列是主键设置.有本事件的列,其左边至少有一列if (position == 0){$(thisobj).parent().prev().focus();}} else if (e.keyCode == 39){// 右 未考虑右边没列的情况,有本事件的列不会处于最右边if (position == $(thisobj).val().length){$(thisobj).parent().next().focus();}} } // 列的类型:获得焦点后,其内生成SELECT function fieldColType_toSelect(thisobj) {if ($(thisobj).find('select').length == 1)return;var selectedval = $(thisobj).attr('val');var select = $('#tpl_fieldcol_type_select').html();$(thisobj).html(select).prop('tabindex', '-1');if (selectedval){$(thisobj).find('select').focus().find('option[value=' + selectedval + ']').prop('selected', 'selected');//console.log($(thisobj).find('select').prop('tabindex')); } } // // 列类型SELECT框失去焦点后,选择值写到val属性上,标题值写到html function fieldType_select_blur(thisobj) {var val = $(thisobj).val();var text = $(thisobj).find('option[value=' + val + ']').html();$(thisobj).parent('.field-col').html(text).attr('val', val).prop('tabindex', '0');// refreshEditStatus(); } // // 列类型SELECT框.按左右方向键时,移动到左右相应列上 function fieldType_select_keydown(event,thisobj) {// 不考虑左右没有列的情况 ,因为本事件所在列不会处在最左或最右var e = event || window.event;if (e.keyCode == 37){// 左移动 $(thisobj).parent().prev().focus();} else if (e.keyCode == 39){// 右 $(thisobj).parent().next().focus();}return false; } // 列的说明:获得焦点后,其内生成textarea function fieldCol_focus_info(thisobj) {if ($(thisobj).find('textarea').length == 1)return;var html = '<textarea class="field-form-control info" οnblur="field_input_blur(this)"></textarea>';var val = $(thisobj).html();$(thisobj).parent('.fieldrow').find('.field-col').addClass('editinfo');$(thisobj).html(html).find('textarea').focus().val(val);$(thisobj).prop('tabindex', '-1'); } // 在当前行的上面插入一行 function fieldColOp_click_insertRow(thisobj) {var row = $(thisobj).parent('.fieldrow');var insertrow = row.before($('#tpl_fieldrow').html()); } // 上移下移操作功能:向上或下移动当前行 function fieldColOp_click_moveRow(thisobj) {var row = $(thisobj).parent();var colindex = row.index();if ($(thisobj).hasClass('moverow-up')){if (colindex > 1){row.insertBefore(row.prev());} } else if ($(thisobj).hasClass('moverow-down')){if (row.next().length == 1)row.insertAfter(row.next());} } // 删除一个列 // 如果没有填写列名,则不提示确认删除 function fieldCopOp_click_delRow(thisobj) {var row = $(thisobj).parent('.fieldrow');var fieldname = row.find('.field-col').eq(1).html();if (fieldname.length == 0){row.remove();refreshEditStatus();return;}var msg = '确定要删除字段 ' + fieldname + ' ?不可恢复!';alertbox(msg,function (){row.remove();refreshEditStatus();}) }