jQuery 具有特定类型的下一个兄弟 child

jQuery next sibling that has a particular kind of child

我的 HTML 有一个包含许多兄弟 div 元素的容器元素,每个兄弟元素包含一个 contenteditable p。然而,这些同级 div 被不包含可编辑元素的其他 div“中断”。

目前我面临的挑战是如何在使用左右箭头键从 C 移动到 D 或从 D 返回 C 时“跳过”这些中断 div(参见代码段) .当遇到这些 div 缺少可编辑元素时,导航将停止。我该如何纠正?

$('#foo p[contenteditable = "true"]').bind("keydown", function(e) {
  switch (e.key) {
    case "ArrowLeft":
      var $P = $(this).parent().prev().children('p[contenteditable = "true"]');
      setTimeout(() => $P.focus(), 0);
      e.stopImmediatePropagation();
      e.preventDefault();
      break;

    case "ArrowRight":
      var $P = $(this).parent().next().children('p[contenteditable = "true"]');
      setTimeout(() => $P.focus(), 0);
      e.stopImmediatePropagation();
      e.preventDefault();
      break;
  }
});
#foo p[contenteditable="true"] {
  /* font-size: 22px;*/
  height: 22px;
  width: 22px;
  color: cyan;
  font-weight: bold;
  background-color: cyan;
}

#foo p.const {
  background-color: inherit;
  border: none;
  height: 22px;
  width: 22px;
}

#foo div {
  text-align: center;
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="foo">
  <div>
    <p contenteditable="true" tabindex="0"></p>
    <p>A</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="1"></p>
    <p>B</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="2"></p>
    <p>C</p>
  </div>
  <div>
    <p class="const">&nbsp;</p>
    <p>&nbsp;</p>
  </div>
  <div>
    <p class="const">"</p>
    <p>"</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="3"></p>
    <p>D</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="4"></p>
    <p>E</p>
  </div>
</div>

代替prev()next(),使用.prevAll(":has(p[contenteditable])").first().nextAll(":has(p[contenteditable])").first()

$('#foo p[contenteditable = "true"]').bind("keydown", function(e) {

  switch (e.key) {
    case "ArrowLeft":
      var $P = $(this).parent().prevAll(":has(p[contenteditable])").first().children('p[contenteditable = "true"]');
      setTimeout(() => $P.focus(), 0);
      e.stopImmediatePropagation();
      e.preventDefault();
      break;

    case "ArrowRight":
      var $P = $(this).parent().nextAll(":has(p[contenteditable])").first().children('p[contenteditable = "true"]');
      setTimeout(() => $P.focus(), 0);
      e.stopImmediatePropagation();
      e.preventDefault();
      break;

  }

});
#foo p[contenteditable="true"] {
  /* font-size: 22px;*/
  height: 22px;
  width: 22px;
  color: cyan;
  font-weight: bold;
  background-color: cyan;
}

#foo p.const {
  background-color: inherit;
  border: none;
  height: 22px;
  width: 22px;
}

#foo div {
  text-align: center;
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="foo">

  <div>
    <p contenteditable="true" tabindex="0"></p>
    <p>A</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="1"></p>
    <p>B</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="2"></p>
    <p>C</p>
  </div>
  <div>
    <p class="const">&nbsp;</p>
    <p>&nbsp;
      <p>
  </div>
  <div>
    <p class="const">"</p>
    <p>"
      <p>
  </div>
  <div>
    <p contenteditable="true" tabindex="3"></p>
    <p>D</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="4"></p>
    <p>E</p>
  </div>



</div>

prevAll/nextAll 匹配所有匹配选择器的 previous/following 兄弟,返回一个 jQuery 对象 按顺序 (所以最接近的兄弟是第一个,然后是第二近的,依此类推);使用 .first() 然后将集合减少到最近的兄弟。

我在其中使用了 :has pseudo-selector,但您最好将相关元素改为 class,因为 :has 是 jQuery-specific 除了 CSS。这里我使用了 class stop 所以它是 .xxxxAll(".stop").first():

$('#foo p[contenteditable = "true"]').bind("keydown", function(e) {

  switch (e.key) {
    case "ArrowLeft":
      var $P = $(this).parent().prevAll(".stop").first().children('p[contenteditable = "true"]');
      setTimeout(() => $P.focus(), 0);
      e.stopImmediatePropagation();
      e.preventDefault();
      break;

    case "ArrowRight":
      var $P = $(this).parent().nextAll(".stop").first().children('p[contenteditable = "true"]');
      setTimeout(() => $P.focus(), 0);
      e.stopImmediatePropagation();
      e.preventDefault();
      break;

  }

});
#foo p[contenteditable="true"] {
  /* font-size: 22px;*/
  height: 22px;
  width: 22px;
  color: cyan;
  font-weight: bold;
  background-color: cyan;
}

#foo p.const {
  background-color: inherit;
  border: none;
  height: 22px;
  width: 22px;
}

#foo div {
  text-align: center;
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="foo">

  <div class="stop">
    <p contenteditable="true" tabindex="0"></p>
    <p>A</p>
  </div>
  <div class="stop">
    <p contenteditable="true" tabindex="1"></p>
    <p>B</p>
  </div>
  <div class="stop">
    <p contenteditable="true" tabindex="2"></p>
    <p>C</p>
  </div>
  <div>
    <p class="const">&nbsp;</p>
    <p>&nbsp;
      <p>
  </div>
  <div>
    <p class="const">"</p>
    <p>"
      <p>
  </div>
  <div class="stop">
    <p contenteditable="true" tabindex="3"></p>
    <p>D</p>
  </div>
  <div class="stop">
    <p contenteditable="true" tabindex="4"></p>
    <p>E</p>
  </div>



</div>

要完成您需要的操作,您需要使用 prevAll()nextAll(),而不是 prev()next(),因为您需要跳过中间元素。您可以将此 :has() 选择器结合起来,以仅查找具有后代 contenteditable 的容器,并与 first() 一起检索最接近的匹配项。

另请注意,bind() 已弃用,将很快从 jQuery 中删除;使用 on() 代替。也可以稍微修改逻辑以减少对 switch 的依赖并避免不必要的重复。最后,您在 HTML.

中的几个地方使用了 <p> 而不是 </p>

话虽如此,试试这个:

let keyMethodLookup = {
  ArrowLeft: 'prevAll',
  ArrowRight: 'nextAll'
}

$('#foo p[contenteditable="true"]').on("keydown", e => {
  if (!keyMethodLookup.hasOwnProperty(e.key))
    return;

  let method = keyMethodLookup[e.key];
  $(e.currentTarget).parent()[method]('div:has(p[contenteditable="true"])').first().children('p[contenteditable="true"]').focus();
  e.stopImmediatePropagation();
  e.preventDefault();
});
#foo p[contenteditable="true"] {
  /* font-size: 22px;*/
  height: 22px;
  width: 22px;
  color: cyan;
  font-weight: bold;
  background-color: cyan;
}

#foo p.const {
  background-color: inherit;
  border: none;
  height: 22px;
  width: 22px;
}

#foo div {
  text-align: center;
  display: inline-block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="foo">
  <div>
    <p contenteditable="true" tabindex="0"></p>
    <p>A</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="1"></p>
    <p>B</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="2"></p>
    <p>C</p>
  </div>
  <div>
    <p class="const">&nbsp;</p>
    <p>&nbsp;</p>
  </div>
  <div>
    <p class="const">"</p>
    <p>"</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="3"></p>
    <p>D</p>
  </div>
  <div>
    <p contenteditable="true" tabindex="4"></p>
    <p>E</p>
  </div>
</div>