简易表格编辑器

SMMS有个建表工具,尝试使用HTML模仿出一个简单的做为练习.

截图:

 

一.实现SMMS建表工具的操作

  1. 点击单元格,进入编辑状态.
  2. 按TAB切换单元格
  3. 按方向箭切换单元格
  4. 按空格设定取消主键列

二.实现思路:

  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)">&#9650;</span>
        <span class="field-col moverow-down noselect" onclick="fieldColOp_click_moveRow(this)">&#9660;</span>
        <span class="field-col insertrow noselect" onclick="fieldColOp_click_insertRow(this)">&#9768;</span>
        <span class="field-col delrow noselect" onclick="fieldCopOp_click_delRow(this)">&#10005;</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>
html
/*
关于列的编辑功能模仿了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();
        })
}
js
原文地址:https://www.cnblogs.com/mirrortom/p/7660577.html