简易表格编辑器
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没做此功能.
四.主要代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
.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; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<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>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 关于列的编辑功能模仿了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" onblur="field_input_blur(this)" onkeydown="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" onblur="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(); }) }