[非]可信的HTML5元素上的键盘事件

时间:2015-11-05 08:49:21

标签: javascript jquery linux html5 contenteditable

我正在对MELT monitor进行编码(自由软件,alpha阶段,与GCC MELT域特定语言相关,以自定义GCC)。它使用libonion作为专门的Web服务器,我希望它成为我正在设计的某些DSL的语法指导编辑器。如果重要的话,我说的是提交97d60053。您可以将其作为./monimelt -Dweb,run -W localhost.localdomain:8086运行,然后在浏览器中打开http://localhost.localdomain:8086/microedit.html

我正在发出(通过文件webroot/microedit.html

<h1>Micro Editing Monimelt</h1>
<div id='microedit_id' contenteditable='true'>*</div>
<hr/>

然后一些AJAX技巧用#micredit_id元素填充包含类似于:

的东西
    <dd class='statval_cl' data-forattr='notice'> &#9653;
    <span class='momnode_cl'>*<span class='momconn_cl'>
    <span class='momitemref_cl'>comment</span></span>
    (&#8220;<span class='momstring_cl'>some simple notice</span>&#8221;
     <span class='momnode_cl'>*<span class='momconn_cl'>
     <span class='momitemref_cl'>web_state</span></span>
     (<span class='momnumber_cl'>2</span>)</span>
     <span class='momitemval_cl'>hashset</span>
     <span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
     <span class='momitemref_cl'>the_agenda</span>}</span>
     <span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
     <span class='momitemref_cl empty_cl'>~</span>
     <span class='momitemref_cl'>the_system</span>]</span>)</span> ;</dd> 

现在,我希望类<span>的每个momitemref_cl某些键盘(可能还有鼠标)事件都很敏感。但是,contenteditable元素可以通过许多用户操作进行编辑(我甚至不了解这些用户操作的完整列表......)和我只希望这些span元素能够响应定义和限制的一组按键(字母数字和空格)并且无法用户更改(例如没有标点字符,没有“剪切”,没有“粘贴” “,没有退格,没有标签等......)。

是否存在contenteditable='true'元素可以获取并正在做出反应的事件(或用户操作)的完整列表?

如何停用大多数这些事件或用户操作(在键盘和鼠标上)并仅 < / strong>(定义明确)键盘事件?

显然,非<span>元素中的contenteditable元素无法获得任何键盘用户操作(因为它无法获得焦点)...

我目标最近的HTML5浏览器,例如Firefox 38或42,或Chrome 47等...如果重要(因此我真的不这么做)在Debian / Linux / x86-64上关心IE9)

PS。 this是一个相关问题,但不是同一个问题。

PS2:找到why contenteditable is terrible博客页面。让我几乎哭了......还读了faking an editable control in browser JavascriptCodeMirror)。另请参阅关于Editing Explaineredit events草案的W3C内部文件草案。 W3C的两件事都在进行中。 UI events上的W3C TR仍然是(2015年11月)工作草案。另请参阅http://jsfiddle.net/8j6jea6p/(在Chrome 46和Firefox 42或43 beta中的行为方式不同)

PS3:也许contenteditable毕竟是一个坏主意。我(遗憾地)考虑使用canvas(àlacarota)并进行所有编辑&amp;用手写的javascript绘图......

附加物:

(2015年11月26日 th

通过私下与一些Mozilla人讨论,我理解:

所以我可能不需要contenteditable

5 个答案:

答案 0 :(得分:4)

你可以这样做:

function validateInput(usrAct){
  swich(usrAct){
    case "paste":
    // do something when pasted
    break;
    case "keydown":
    // dosomething on keydown
    break;
    default:
    //do something on default
    break;
  }
}

document.querySelectorAll('.momitemref_cl').addEventListener('input', function(e){
  validateInput(e.type)
}, false);

答案 1 :(得分:3)

这个代码段可能就是您要找的内容,使span.momitemref_cl个元素可以关注,但不能列表,并且设置为contenteditable。但是,当我在chrome上测试它时,contenteditable在属性contenteditable设置为true的任何容器内,不要触发任何键盘事件。所以诀窍可能在焦点上将任何容器设置为不可编辑(并切换回模糊)。

参见例如:(keypress和keydown事件都被绑定以处理某些特定情况,其中keypress或keydown不会在特定键上触发)

注意: 您似乎在动态填充DIV内容,您可以委托它或绑定事件(如果在HTML标记中更改它而不是解决方案,则设置tabindex属性)ajax请求已完成。

&#13;
&#13;
$('#microedit_id .momitemref_cl').attr('tabindex', -1).prop('contenteditable', true).on('focusin focusout', function(e) {
  $(this).parents('[contenteditable]').prop('contenteditable', e.type === "focusout");
}).on('keypress keydown paste cut', function(e) {
  if (/[a-zA-Z0-9 ]/.test(String.fromCharCode(e.which))) return;
  return false;
});
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<h1>Micro Editing Monimelt</h1>

<div id='microedit_id' contenteditable='true'>
  <dd class='statval_cl' data-forattr='notice'>&#9653; <span class='momnode_cl'>*<span class='momconn_cl'>
    <span class='momitemref_cl'>comment</span></span>(&#8220;<span class='momstring_cl'>some simple notice</span>&#8221; <span class='momnode_cl'>*<span class='momconn_cl'>
     <span class='momitemref_cl'>web_state</span></span>(<span class='momnumber_cl'>2</span>)</span> <span class='momitemval_cl'>hashset</span>
    <span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
    <span class='momitemref_cl'>the_agenda</span>}</span> <span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
    <span class='momitemref_cl empty_cl'>~</span>
    <span class='momitemref_cl'>the_system</span>]</span>)</span>;</dd>
</div>
<hr/>
&#13;
&#13;
&#13;

答案 2 :(得分:2)

首先,当您将HTMLElements属性设置为contentEditableElements时,contentEditable变为true

现在,解析IMO的最佳方法是收听inputEvent并检查您的元素textContent

s.addEventListener('input', validate, false);

function validate(evt) {
  var badValues = ['bad', 'content'];
  var span = this;
  badValues.forEach(function(v) {
    if (span.textContent.indexOf(v) > -1) {
      // that's bad m..key
      span.textContent = span.textContent.split(v).join('');
    }
  });
};
<span id="s" contentEditable="true">Hello</span>

不幸的是,输入事件不是widely supported 因此,您可能需要添加onkeydownonpaste以及onclick个事件处理程序来捕获不支持的浏览器(a.k.a IE)。

答案 3 :(得分:2)

编辑:

只处理具有所述类的跨度。还处理案例,您可以从另一个跨度返回到前一个并且可以删除它。结合@AWolff的想法来切换contenteditable属性集中

总体思路与之前版本保持一致。

小提琴:http://jsfiddle.net/abhitalks/gb0mbwLu/

<强>段:

var div = document.getElementById('microedit_id'), 
    spans = document.querySelectorAll('#microedit_id .momitemref_cl'), 
    commands = ['paste', 'cut'], 
    // whitelist is the keycodes for keypress event
    whitelist = [{'range': true, 'start': '97', 'end': '122'}, // lower-case
                 {'range': true, 'start': '65', 'end': '90'}, // upper-case
                 {'range': true, 'start': '48', 'end': '57' } // numbers
	], 
    // specialkeys is the keycodes for keydown event
    specialKeys = [8, 9, 13, 46] // backspace, tab, enter, delete
;
div.addEventListener('keydown', handleFromOutside, false);

[].forEach.call(spans, function(span) {
    span.setAttribute('contenteditable', true);
    span.setAttribute('tabindex', '-1');
    span.addEventListener('focus', handleFocus, false);
    span.addEventListener('blur', handleBlur, false);
    commands.forEach(function(cmd) {
        span.addEventListener(cmd, function(e) {
            e.preventDefault(); return false;
        });
    });
    span.addEventListener('keypress', handlePress, false);
    span.addEventListener('keydown', handleDown, false);
});

function handleFocus(e) { div.setAttribute('contenteditable', false); }
function handleBlur(e) { div.setAttribute('contenteditable', true); }

function handlePress(e) {
    var allowed = false, key = e.keyCode;
    whitelist.forEach(function(range) {
        if (key && (key != '') && (range.start <= key) && (key <= range.end)) {
            allowed = true;
        }
    });
    if (! allowed) { e.preventDefault(); return false; }
}

function handleDown(e) {
    var allowed = false, key = e.keyCode;
    specialKeys.forEach(function(spl) {
        if (key && (spl == key)) { e.preventDefault(); return false; }
    });
}

function handleFromOutside(e) {
    var key = e.keyCode, node = window.getSelection().anchorNode, prev, next;
    node = (node.nodeType == 3 ? node.parentNode : node)
    prev = node.previousSibling; next = node.nextSibling; 
    if (prev || next) {
        if (node.className == 'momitemref_cl') {
            if (specialKeys.indexOf(key) >= 0) {
                e.preventDefault(); return false;
            }
        }
    }
}
<h1>Micro Editing Monimelt</h1>
<div id='microedit_id' contenteditable='true'>
    <dd class='statval_cl' data-forattr='notice'> &#9653;
    <span class='momnode_cl'>*<span class='momconn_cl'>
    <span class='momitemref_cl'>comment</span></span>
    (&#8220;<span class='momstring_cl'>some simple notice</span>&#8221;
     <span class='momnode_cl'>*<span class='momconn_cl'>
     <span class='momitemref_cl'>web_state</span></span>
     (<span class='momnumber_cl'>2</span>)</span>
     <span class='momitemval_cl'>hashset</span>
     <span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
     <span class='momitemref_cl'>the_agenda</span>}</span>
     <span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
     <span class='momitemref_cl empty_cl'>~</span>
     <span class='momitemref_cl'>the_system</span>]</span>)</span> ;</dd>     
</div>
<hr/>

除了通常处理跨度上的事件以及阻止/允许来自白名单和黑名单的键和/或命令之外;这段代码的作用是检查光标或编辑当前是否正在其他不受约束的跨度上完成。当使用箭头键从目标跨度中选择或移动时,我们禁止使用特殊键来防止删除等。

function handleFromOutside(e) {
    var key = e.keyCode, node = window.getSelection().anchorNode, prev, next;
    node = (node.nodeType == 3 ? node.parentNode : node)
    prev = node.previousSibling; next = node.nextSibling; 
    if (prev || next) {
        if (node.className == 'momitemref_cl') {
            if (specialKeys.indexOf(key) >= 0) {
                e.preventDefault(); return false;
            }
        }
    }
}

我没有多少时间,因此仍然存在一个问题。并且,这是在从外部移动到目标跨度时禁止命令以及剪切和粘贴。

较旧版本仅供参考:

如果要允许的所有击键,您可以维护一个白名单(或黑名单,如果允许的命令数量更高)。同样,还要维护要阻止的所有事件的字典。

然后连接div上的命令并使用event.preventDefault()拒绝该操作。接下来,连接keydown事件并使用白名单允许所有击键都在上面定义的允许范围内:

在下面的示例中,根据第一个范围,只允许使用数字和字母,并且根据第二个范围允许使用箭头键(以及pageup / down和space )。休息所有行动都被阻止/拒绝。

然后,您可以将其进一步扩展到您的用例。在下面的演示中试一试。

小提琴:http://jsfiddle.net/abhitalks/re7ucgra/

<强>段:

var div = document.getElementById('microedit_id'), 
    spans = document.querySelectorAll('#microedit_id span'), 
    commands = ['paste'], 
    whitelist = [ {'start': 48, 'end': 90}, {'start': 32, 'end': 40 }, ]
;
commands.forEach(function(cmd) {
    div.addEventListener(cmd, function(e) {
        e.preventDefault(); return false;
    });
});

div.addEventListener('keydown', handleKeys, false);

function handleKeys(e) {
    var allowed = false;
    whitelist.forEach(function(range) {
        if ((range.start <= e.keyCode) && (e.keyCode <= range.end)) {
            allowed = true;
        }
    });
    if (! allowed) { e.preventDefault(); return false; }
};
<h1>Micro Editing Monimelt</h1>
<div id='microedit_id' contenteditable='true'>
    <dd class='statval_cl' data-forattr='notice'> &#9653;
    <span class='momnode_cl'>*<span class='momconn_cl'>
    <span class='momitemref_cl'>comment</span></span>
    (&#8220;<span class='momstring_cl'>some simple notice</span>&#8221;
     <span class='momnode_cl'>*<span class='momconn_cl'>
     <span class='momitemref_cl'>web_state</span></span>
     (<span class='momnumber_cl'>2</span>)</span>
     <span class='momitemval_cl'>hashset</span>
     <span class='momset_cl'>{<span class='momitemref_cl'>microedit</span>
     <span class='momitemref_cl'>the_agenda</span>}</span>
     <span class='momtuple_cl'>[<span class='momitemref_cl'>web_session</span>
     <span class='momitemref_cl empty_cl'>~</span>
     <span class='momitemref_cl'>the_system</span>]</span>)</span> ;</dd>     
</div>
<hr/>

已编辑,以解决不捕获特殊键的问题,尤其是在按下shift并且为keyCode生成相同的keypress时。添加了keydown来处理特殊密钥。

注意: 这假设要发生在整个div上。正如我在问题中看到的那样,只有span s和嵌套的那些。没有其他元素。如果涉及其他元素并且您想要免除这些元素,那么您将需要仅将事件绑定到这些元素。这是因为,当父亲为contenteditable并且没有对孩子开枪时,父母会捕捉孩子的事件。

答案 4 :(得分:1)

一种直接解决问题的方法是侦听最里面的元素触发的keydown事件,并采取相应的措施。可以在下面找到示例代码片段:

HTML:

<div class="momitemref_cl" contenteditable="true">Foo Bar</div>
<input class="not-momitemref_cl"/>
<input class="momitemref_cl"/>

JS:

document.querySelectorAll('.momitemref_cl').forEach((el) => {
    el.addEventListener('keydown', validateInput);
    el.addEventListener('cut', e => e.preventDefault());
    el.addEventListener('copy', e => e.preventDefault());
    el.addEventListener('paste', e => e.preventDefault());
});

function validateInput(userAction) {
    console.log(userAction);
    if (userAction.ctrlKey) {
        userAction.preventDefault();
        return false;
    }
    let code = (userAction.keyCode ? userAction.keyCode : userAction.which);
    if ((48 <= code && code <= 57 && !userAction.shiftKey) || (65 <= code && code <= 90) || (97 <= code && code <= 122) || code === 32) {
        console.log(`Allowing keypress with code: ${code}`);
        return true;
    }
    console.log(`Preventing keypress with code: ${code}`);
    userAction.preventDefault();
    return false;
}

这对<input>元素以及将contenteditable属性设置为true的元素都有效。

JS小提琴:https://jsfiddle.net/rsjw3c87/22/

编辑:还添加了其他检查,以防止右键单击并复制/剪切/粘贴。直接通过contextmenu事件禁用右键单击不起作用,因为某些浏览器和操作系统不允许您禁用该特定事件。