โฟกัสองค์ประกอบถัดไปในดัชนีแท็บ


109

ฉันกำลังพยายามย้ายโฟกัสไปยังองค์ประกอบถัดไปในลำดับแท็บตามองค์ประกอบปัจจุบันที่มีโฟกัส ป่านนี้ฉันยังไม่พบอะไรในการค้นหา

function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    currentElementId = "";
    currentElement.nextElementByTabIndex.focus();
}

แน่นอน nextElementByTabIndex เป็นส่วนสำคัญสำหรับการทำงานนี้ ฉันจะค้นหาองค์ประกอบถัดไปในลำดับแท็บได้อย่างไร การแก้ปัญหาจะต้องใช้ JScript ไม่ใช่ JQuery


3
ทำไมคุณถึงมีเส้นนี้currentElementId = "";?

1
ฉันไม่คิดว่าเบราว์เซอร์ใด ๆ จะเปิดเผยข้อมูลการสั่งซื้อแท็บและอัลกอริทึมที่เบราว์เซอร์ใช้เองนั้นซับซ้อนเกินกว่าจะทำซ้ำได้ บางทีคุณอาจจะสามารถ จำกัด การความต้องการของคุณเช่น "พิจารณาเฉพาะinput, buttonและtextareaแท็กและไม่สนใจtabindexแอตทริบิวต์"
Wladimir Palant

เราจำเป็นต้องเห็น.newElementByTabIndexรหัสของคุณเพราะนั่นคือสิ่งที่ไม่ทำงาน
0x499602D2

2
จากนั้นอีกครั้งบางทีการ จำกัด แท็กบางแท็กอาจไม่จำเป็นเราสามารถตรวจสอบได้ว่ามีfocus()วิธีการอยู่หรือไม่
Wladimir Palant

2
@ เดวิดนั่นคือฟังก์ชั่นที่ไม่มีอยู่ดังนั้นคำถามของฉัน : D
JadziaMD

คำตอบ:


25

หากไม่มี jquery: ก่อนอื่นในองค์ประกอบที่สามารถแท็บของคุณการเพิ่มclass="tabable"สิ่งนี้จะทำให้เราเลือกได้ในภายหลัง (อย่าลืมคำนำหน้าตัวเลือกคลาส "." ในโค้ดด้านล่าง)

var lastTabIndex = 10;
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFOcusIn
    var curIndex = currentElement.tabIndex; //get current elements tab index
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    var tabbables = document.querySelectorAll(".tabable"); //get all tabable elements
    for(var i=0; i<tabbables.length; i++) { //loop through each element
        if(tabbables[i].tabIndex == (curIndex+1)) { //check the tabindex to see if it's the element we want
            tabbables[i].focus(); //if it's the one we want, focus it and exit the loop
            break;
        }
    }
}

19
วิธีแก้ปัญหาโดยไม่ต้องเพิ่มชื่อให้กับทุกองค์ประกอบ (เนื่องจากมีหลายวิธีที่จะทำให้เป็นไปได้) จะเหมาะอย่างยิ่ง
JadziaMD

3
ตกลงนี่เป็นแบบฟอร์มหรือไม่? ถ้าทุกองค์ประกอบที่คุณต้องการเป็นองค์ประกอบการป้อนข้อมูลคุณสามารถแทนที่บรรทัดvar tabbables = document.getElementsByName("tabable");กับvar tabbables = document.getElementsByTagName("input");แทน
ไบรอัน Glaz

var tabbables = document.querySelectorAll("input, textarea, button")// IE8 + รับการอ้างอิงถึง tabbables ทั้งหมดโดยไม่ต้องแก้ไข HTML ของคุณ
Greg

2
class = "tabbable" แทนที่จะใช้แอตทริบิวต์ name
Chris F Carroll

4
โปรดทราบว่าการใช้ flexbox ลำดับขององค์ประกอบใน DOM จะแตกต่างจากที่มองเห็นได้ในเบราว์เซอร์ เพียงแค่เลือกองค์ประกอบแท็บถัดไปจะไม่ทำงานเมื่อคุณเปลี่ยนลำดับขององค์ประกอบโดยใช้ flexbox
Haneev

76

ฉันไม่เคยใช้สิ่งนี้ แต่ฉันได้ตรวจสอบปัญหาที่คล้ายกันและนี่คือสิ่งที่ฉันจะลอง

ลองทำตามนี้ก่อน

ขั้นแรกฉันจะดูว่าคุณสามารถเริ่มkeypressเหตุการณ์สำหรับปุ่ม Tab บนองค์ประกอบที่โฟกัสอยู่ในขณะนี้ได้หรือไม่ อาจมีหลายวิธีในการทำเช่นนี้สำหรับเบราว์เซอร์ที่แตกต่างกัน

หากไม่ได้ผลคุณจะต้องทำงานให้หนักขึ้น ...

การอ้างถึงการใช้งาน jQuery คุณต้อง:

  1. ฟัง Tab และ Shift + Tab
  2. รู้ว่าองค์ประกอบใดที่สามารถใช้แท็บได้
  3. ทำความเข้าใจว่าลำดับแท็บทำงานอย่างไร

1. ฟัง Tab และ Shift + Tab

การฟัง Tab และ Shift + Tab น่าจะครอบคลุมที่อื่น ๆ บนเว็บดังนั้นฉันจะข้ามส่วนนั้นไป

2. รู้ว่าองค์ประกอบใดที่สามารถแท็บได้

การรู้ว่าองค์ประกอบใดบ้างที่สามารถใช้แท็บได้นั้นยากกว่า โดยทั่วไปองค์ประกอบจะสามารถใช้แท็บได้หากสามารถโฟกัสได้และไม่มีtabindex="-1"ชุดแอตทริบิวต์ ดังนั้นเราต้องถามว่าองค์ประกอบใดที่สามารถโฟกัสได้ องค์ประกอบต่อไปนี้สามารถโฟกัสได้:

  • input, select, textarea, buttonและobjectองค์ประกอบที่ไม่พิการ
  • aและareaองค์ประกอบที่มีhrefหรือมีค่าตัวเลขสำหรับtabindexชุด
  • องค์ประกอบใด ๆ ที่มีค่าตัวเลขสำหรับtabindexset

นอกจากนี้องค์ประกอบยังสามารถโฟกัสได้ก็ต่อเมื่อ:

  • บรรพบุรุษของมันdisplay: noneไม่มี
  • ราคาคำนวณของมีvisibility visibleซึ่งหมายความว่าบรรพบุรุษที่ใกล้ที่สุดที่จะมีการตั้งต้องมีค่าของvisibility visibleถ้าไม่มีบรรพบุรุษได้ตั้งแล้วราคาคำนวณเป็นvisibilityvisible

รายละเอียดเพิ่มเติมมีอีกคำตอบที่กองมากเกิน

3. ทำความเข้าใจว่าลำดับแท็บทำงานอย่างไร

ลำดับแท็บขององค์ประกอบในเอกสารถูกควบคุมโดยtabindexแอตทริบิวต์ ถ้าไม่มีค่าถูกตั้งค่าได้อย่างมีประสิทธิภาพtabindex0

tabindexเพื่อให้เอกสารคือ 1, 2, 3, ... , 0

ในขั้นต้นเมื่อbodyองค์ประกอบ (หรือองค์ประกอบไม่ได้) มีโฟกัส, tabindexองค์ประกอบแรกในการสั่งซื้อแท็บเป็นที่ต่ำที่สุดที่ไม่ใช่ศูนย์ หากหลายองค์ประกอบมีเหมือนกันtabindexให้คุณเรียงลำดับเอกสารจนกว่าจะถึงองค์ประกอบสุดท้ายด้วยสิ่งtabindexนั้น จากนั้นคุณเลื่อนไปต่ำสุดถัดไปtabindexและกระบวนการจะดำเนินต่อไป สุดท้ายจบด้วยองค์ประกอบเหล่านั้นกับศูนย์ tabindex(หรือเปล่า)


38

นี่คือสิ่งที่ฉันสร้างขึ้นเพื่อจุดประสงค์นี้:

focusNextElement: function () {
    //add all elements we want to include in our selection
    var focussableElements = 'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';
    if (document.activeElement && document.activeElement.form) {
        var focussable = Array.prototype.filter.call(document.activeElement.form.querySelectorAll(focussableElements),
        function (element) {
            //check for visibility while always include the current activeElement 
            return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
        });
        var index = focussable.indexOf(document.activeElement);
        if(index > -1) {
           var nextElement = focussable[index + 1] || focussable[0];
           nextElement.focus();
        }                    
    }
}

คุณสมบัติ:

  • ชุดองค์ประกอบที่โฟกัสได้ที่กำหนดได้
  • ไม่จำเป็นต้องใช้ jQuery
  • ใช้งานได้กับเบราว์เซอร์สมัยใหม่ทั้งหมด
  • เร็วและน้ำหนักเบา

2
นี่คือโซลูชันที่มีประสิทธิภาพและเป็นมิตรต่อทรัพยากรมากที่สุด ขอบคุณ! นี่คือสคริปต์การทำงานแบบเต็มของฉัน: stackoverflow.com/a/40686327/1589669
eapo

ฉันเพิ่มตัวอย่างข้อมูลด้านล่างเพื่อรวมการจัดเรียงตาม TabIndex focussable.sort (sort_by_TabIndex) ที่
ชัดเจน

1
ที่สุด ! มันต้องซับซ้อนขนาดนั้น: nextElementSiblingอาจจะโฟกัสไม่ได้ส่วนที่โฟกัสได้ถัดไปอาจไม่ใช่พี่น้อง
Tinmarino

วิธีการที่ดี แต่ควรอนุญาตให้มีการป้อนข้อมูลใด ๆ ซึ่งไม่ได้เป็นชนิดhiddenและยังครอบคลุมและtextarea select
Lucero

23

ฉันสร้างปลั๊กอิน jQuery ง่ายๆซึ่งทำสิ่งนี้ได้ ใช้ตัวเลือก ': tabbable' ของ jQuery UI เพื่อค้นหาองค์ประกอบ 'tabbable' ถัดไปและเลือก

ตัวอย่างการใช้งาน:

// Simulate tab key when element is clicked 
$('.myElement').bind('click', function(event){
    $.tabNext();
    return false;
});

8

แก่นของคำตอบอยู่ที่การค้นหาองค์ประกอบต่อไป:

  function findNextTabStop(el) {
    var universe = document.querySelectorAll('input, button, select, textarea, a[href]');
    var list = Array.prototype.filter.call(universe, function(item) {return item.tabIndex >= "0"});
    var index = list.indexOf(el);
    return list[index + 1] || list[0];
  }

การใช้งาน:

var nextEl = findNextTabStop(element);
nextEl.focus();

tabIndexขอให้สังเกตฉันไม่สนใจเกี่ยวกับการจัดลำดับความสำคัญ


3
จะเกิดอะไรขึ้นหากคำสั่ง tabindex ขัดกับลำดับเอกสาร ฉันคิดว่าอาร์เรย์จะต้องเรียงตามหมายเลขแท็บดัชนีตามลำดับเอกสาร
Chris F Carroll

ใช่นั่นน่าจะ "เป็นไปตามข้อกำหนด" มากกว่า ฉันไม่แน่ใจเกี่ยวกับ edge-case เกี่ยวกับองค์ประกอบหลัก ฯลฯ
André Werlang

จะเกิดอะไรขึ้นหากรายการที่ไม่ใช่หนึ่งในแท็กเหล่านั้นมีแอตทริบิวต์ tabindex
Matt Pennington

1
@MattPennington มันจะถูกละเลย ตัวกรองคือ (ความพยายาม) เพื่อเร่งความเร็วในการค้นหาอย่าลังเลที่จะปรับตัว
André Werlang

3

ดังที่ได้กล่าวไว้ในความคิดเห็นข้างต้นฉันไม่คิดว่าเบราว์เซอร์ใด ๆ จะเปิดเผยข้อมูลการสั่งซื้อแท็บ ต่อไปนี้เป็นการประมาณอย่างง่ายของสิ่งที่เบราว์เซอร์ทำเพื่อรับองค์ประกอบถัดไปตามลำดับแท็บ:

var allowedTags = {input: true, textarea: true, button: true};

var walker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT,
  {
    acceptNode: function(node)
    {
      if (node.localName in allowedTags)
        return NodeFilter.FILTER_ACCEPT;
      else
        NodeFilter.FILTER_SKIP;
    }
  },
  false
);
walker.currentNode = currentElement;
if (!walker.nextNode())
{
  // Restart search from the start of the document
  walker.currentNode = walker.root;
  walker.nextNode();
}
if (walker.currentNode && walker.currentNode != walker.root)
  walker.currentNode.focus();

สิ่งนี้จะพิจารณาเฉพาะบางแท็กและละเว้นtabindexแอตทริบิวต์ แต่อาจเพียงพอขึ้นอยู่กับสิ่งที่คุณพยายามบรรลุ


3

ดูเหมือนว่าคุณสามารถตรวจสอบtabIndexคุณสมบัติขององค์ประกอบเพื่อพิจารณาว่าสามารถโฟกัสได้หรือไม่ องค์ประกอบที่ไม่สามารถโฟกัสได้มีค่าเป็นtabindex"-1"

จากนั้นคุณก็ต้องรู้กฎสำหรับการหยุดแท็บ:

  • tabIndex="1" มีรางวัลสูงสุด
  • tabIndex="2" มีลำดับความสำคัญสูงสุดถัดไป
  • tabIndex="3" อยู่ถัดไปและอื่น ๆ
  • tabIndex="0" (หรือ tabbable โดยค่าเริ่มต้น) มีลำดับความสำคัญต่ำสุด
  • tabIndex="-1" (หรือไม่ใช้แท็บตามค่าเริ่มต้น) จะไม่ทำหน้าที่เป็นตัวหยุดแท็บ
  • สำหรับสององค์ประกอบที่มี tabIndex เดียวกันองค์ประกอบที่ปรากฏก่อนใน DOM จะมีลำดับความสำคัญสูงกว่า

นี่คือตัวอย่างวิธีสร้างรายการหยุดแท็บตามลำดับโดยใช้ Javascript แท้:

function getTabStops(o, a, el) {
    // Check if this element is a tab stop
    if (el.tabIndex > 0) {
        if (o[el.tabIndex]) {
            o[el.tabIndex].push(el);
        } else {
            o[el.tabIndex] = [el];
        }
    } else if (el.tabIndex === 0) {
        // Tab index "0" comes last so we accumulate it seperately
        a.push(el);
    }
    // Check if children are tab stops
    for (var i = 0, l = el.children.length; i < l; i++) {
        getTabStops(o, a, el.children[i]);
    }
}

var o = [],
    a = [],
    stops = [],
    active = document.activeElement;

getTabStops(o, a, document.body);

// Use simple loops for maximum browser support
for (var i = 0, l = o.length; i < l; i++) {
    if (o[i]) {
        for (var j = 0, m = o[i].length; j < m; j++) {
            stops.push(o[i][j]);
        }
    }
}
for (var i = 0, l = a.length; i < l; i++) {
    stops.push(a[i]);
}

ก่อนอื่นเราเดิน DOM โดยรวบรวมแท็บทั้งหมดตามลำดับด้วยดัชนี จากนั้นเราจะรวบรวมรายชื่อสุดท้าย สังเกตว่าเราเพิ่มรายการtabIndex="0"ที่อยู่ท้ายสุดของรายการหลังรายการด้วยtabIndex1, 2, 3 เป็นต้น

สำหรับตัวอย่างที่ใช้งานได้เต็มรูปแบบซึ่งคุณสามารถแท็บโดยใช้ปุ่ม "enter" ให้ดูที่ซอนี้


3

Tabbableเป็นแพคเกจ JS ขนาดเล็กที่ช่วยให้คุณมีรายชื่อขององค์ประกอบ tabbable ทั้งหมดในการสั่งซื้อแท็บ ดังนั้นคุณจะพบองค์ประกอบของคุณในรายการนั้นจากนั้นมุ่งเน้นไปที่รายการถัดไป

แพคเกจจัดการกรณีขอบที่ซับซ้อนที่กล่าวถึงในคำตอบอื่น ๆ ได้อย่างถูกต้อง (เช่นไม่มีบรรพบุรุษก็ได้display: none) และมันไม่ได้ขึ้นอยู่กับ jQuery!

จากการเขียนนี้ (เวอร์ชัน 1.1.1) มีข้อแม้ว่าไม่รองรับ IE8 และข้อบกพร่องของเบราว์เซอร์ทำให้ไม่สามารถจัดการcontenteditableได้อย่างถูกต้อง


2
function focusNextElement(){
  var focusable = [].slice.call(document.querySelectorAll("a, button, input, select, textarea, [tabindex], [contenteditable]")).filter(function($e){
    if($e.disabled || ($e.getAttribute("tabindex") && parseInt($e.getAttribute("tabindex"))<0)) return false;
    return true;
  }).sort(function($a, $b){
    return (parseFloat($a.getAttribute("tabindex") || 99999) || 99999) - (parseFloat($b.getAttribute("tabindex") || 99999) || 99999);
  });
  var focusIndex = focusable.indexOf(document.activeElement);
  if(focusable[focusIndex+1]) focusable[focusIndex+1].focus();
};

1

นี่เป็นโพสต์แรกของฉันใน SO ดังนั้นฉันจึงไม่มีชื่อเสียงมากพอที่จะแสดงความคิดเห็นกับคำตอบที่ยอมรับได้ แต่ฉันต้องแก้ไขโค้ดดังต่อไปนี้:

export function focusNextElement () {
  //add all elements we want to include in our selection
  const focussableElements = 
    'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled])'
  if (document.activeElement && document.activeElement.form) {
      var focussable = Array.prototype.filter.call(
        document.activeElement.form.querySelectorAll(focussableElements),
      function (element) {
          // if element has tabindex = -1, it is not focussable
          if ( element.hasAttribute('tabindex') && element.tabIndex === -1 ){
            return false
          }
          //check for visibility while always include the current activeElement 
          return (element.offsetWidth > 0 || element.offsetHeight > 0 || 
            element === document.activeElement)
      });
      console.log(focussable)
      var index = focussable.indexOf(document.activeElement);
      if(index > -1) {
         var nextElement = focussable[index + 1] || focussable[0];
         console.log(nextElement)
         nextElement.focus()
      }                    
  }
}

การเปลี่ยน var เป็นค่าคงที่ไม่สำคัญ การเปลี่ยนแปลงหลักคือเรากำจัดตัวเลือกที่ตรวจสอบ tabindex! = "-1" หลังจากนั้นหากองค์ประกอบนั้นมีแอตทริบิวต์ tabindex และตั้งค่าเป็น "-1" เราจะไม่พิจารณา

เหตุผลที่ฉันต้องเปลี่ยนเนื่องจากเมื่อเพิ่ม tabindex = "- 1" ไปยัง<input>องค์ประกอบนี้ยังถือว่าอยู่ในโฟกัสได้เนื่องจากตรงกับตัวเลือก "input [type = text]: not ([disabled])" การเปลี่ยนแปลงของฉันเทียบเท่ากับ "ถ้าเราเป็นผู้ป้อนข้อความที่ไม่ได้ปิดใช้งานและเรามีแอตทริบิวต์ tabIndex และค่าของแอตทริบิวต์นั้นคือ -1 ดังนั้นเราจึงไม่ควรถือเป็นจุดโฟกัส

ฉันเชื่อว่าเมื่อผู้เขียนคำตอบที่ได้รับการยอมรับแก้ไขคำตอบสำหรับบัญชีสำหรับแอตทริบิวต์ tabIndex พวกเขาทำไม่ถูกต้อง โปรดแจ้งให้เราทราบหากไม่เป็นเช่นนั้น


1

มีคุณสมบัติtabindexที่สามารถตั้งค่าบนคอมโพเนนต์ได้ ระบุลำดับที่องค์ประกอบอินพุตควรจะวนซ้ำเมื่อเลือกหนึ่งรายการและกดแท็บ ค่าที่สูงกว่า 0 สงวนไว้สำหรับการนำทางที่กำหนดเอง 0 คือ "ตามลำดับธรรมชาติ" (ดังนั้นจะทำงานแตกต่างกันหากตั้งค่าสำหรับองค์ประกอบแรก) -1 หมายถึงแป้นพิมพ์ที่โฟกัสไม่ได้:

<!-- navigate with tab key: -->
<input tabindex="1" type="text"/>
<input tabindex="2" type="text"/>

นอกจากนี้ยังสามารถตั้งค่าเป็นอย่างอื่นนอกเหนือจากช่องใส่ข้อความได้ แต่ยังไม่ชัดเจนว่าจะทำอะไรที่นั่นถ้ามีอะไรเลย แม้ว่าการนำทางจะใช้งานได้ แต่อาจจะดีกว่าที่จะใช้ "ลำดับธรรมชาติ" สำหรับสิ่งอื่นที่ไม่ใช่องค์ประกอบการป้อนข้อมูลของผู้ใช้ที่ชัดเจนมาก

ไม่คุณไม่จำเป็นต้องใช้ JQuery หรือสคริปต์ใด ๆ เลยเพื่อรองรับเส้นทางการนำทางที่กำหนดเองนี้ คุณสามารถใช้งานได้ที่ฝั่งเซิร์ฟเวอร์โดยไม่ต้องรองรับ JavaScript จากอีกด้านหนึ่งคุณสมบัติยังทำงานได้ดีในกรอบการตอบสนอง แต่ไม่จำเป็นต้องใช้


0

นี่คือเวอร์ชันที่สมบูรณ์ยิ่งขึ้นของการมุ่งเน้นไปที่องค์ประกอบถัดไป เป็นไปตามหลักเกณฑ์ข้อกำหนดและจัดเรียงรายการองค์ประกอบอย่างถูกต้องโดยใช้ tabindex นอกจากนี้ยังมีการกำหนดตัวแปรย้อนกลับหากคุณต้องการรับองค์ประกอบก่อนหน้า

function focusNextElement( reverse, activeElem ) {
  /*check if an element is defined or use activeElement*/
  activeElem = activeElem instanceof HTMLElement ? activeElem : document.activeElement;

  let queryString = [
      'a:not([disabled]):not([tabindex="-1"])',
      'button:not([disabled]):not([tabindex="-1"])',
      'input:not([disabled]):not([tabindex="-1"])',
      'select:not([disabled]):not([tabindex="-1"])',
      '[tabindex]:not([disabled]):not([tabindex="-1"])'
      /* add custom queries here */
    ].join(','),
    queryResult = Array.prototype.filter.call(document.querySelectorAll(queryString), elem => {
      /*check for visibility while always include the current activeElement*/
      return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem === activeElem;
    }),
    indexedList = queryResult.slice().filter(elem => {
      /* filter out all indexes not greater than 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? false : true;
    }).sort((a, b) => {
      /* sort the array by index from smallest to largest */
      return a.tabIndex != 0 && b.tabIndex != 0 
        ? (a.tabIndex < b.tabIndex ? -1 : b.tabIndex < a.tabIndex ? 1 : 0) 
        : a.tabIndex != 0 ? -1 : b.tabIndex != 0 ? 1 : 0;
    }),
    focusable = [].concat(indexedList, queryResult.filter(elem => {
      /* filter out all indexes above 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? true : false;
    }));

  /* if reverse is true return the previous focusable element
     if reverse is false return the next focusable element */
  return reverse ? (focusable[focusable.indexOf(activeElem) - 1] || focusable[focusable.length - 1]) 
    : (focusable[focusable.indexOf(activeElem) + 1] || focusable[0]);
}

0

นี่คือการเพิ่มศักยภาพของโซลูชันที่ยอดเยี่ยมที่ @Kanoและ@Mxนำเสนอ หากคุณต้องการรักษาการจัดลำดับ TabIndex ให้เพิ่มการจัดเรียงนี้ตรงกลาง:

// Sort by explicit Tab Index, if any
var sort_by_TabIndex = function (elementA, elementB) {
    let a = elementA.tabIndex || 1;
    let b = elementB.tabIndex || 1;
    if (a < b) { return -1; }
    if (a > b) { return 1; }
    return 0;
}
focussable.sort(sort_by_TabIndex);

0

คุณสามารถเรียกสิ่งนี้ว่า:

แท็บ:

$.tabNext();

Shift + Tab:

$.tabPrev();

<!DOCTYPE html>
<html>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<script>
(function($){
	'use strict';

	/**
	 * Focusses the next :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.focusNext = function(){
		selectNextTabbableOrFocusable(':focusable');
	};

	/**
	 * Focusses the previous :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.focusPrev = function(){
		selectPrevTabbableOrFocusable(':focusable');
	};

	/**
	 * Focusses the next :tabable element.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.tabNext = function(){
		selectNextTabbableOrFocusable(':tabbable');
	};

	/**
	 * Focusses the previous :tabbable element
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.tabPrev = function(){
		selectPrevTabbableOrFocusable(':tabbable');
	};

    function tabIndexToInt(tabIndex){
        var tabIndexInded = parseInt(tabIndex);
        if(isNaN(tabIndexInded)){
            return 0;
        }else{
            return tabIndexInded;
        }
    }

    function getTabIndexList(elements){
        var list = [];
        for(var i=0; i<elements.length; i++){
            list.push(tabIndexToInt(elements.eq(i).attr("tabIndex")));
        }
        return list;
    }

    function selectNextTabbableOrFocusable(selector){
        var selectables = $(selector);
        var current = $(':focus');

        // Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex+1; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return a-b});
        if(currentTabIndex === tabIndexList[tabIndexList.length-1]){
            currentTabIndex = -1;// Starting from 0
        }

        // Find next TabIndex of all element
        var nextTabIndex = tabIndexList.find(function(element){return currentTabIndex<element;});
        for(var i=0; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === nextTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
    }

	function selectPrevTabbableOrFocusable(selector){
		var selectables = $(selector);
		var current = $(':focus');

		// Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return b-a});
        if(currentTabIndex <= tabIndexList[tabIndexList.length-1]){
            currentTabIndex = tabIndexList[0]+1;// Starting from max
        }

        // Find prev TabIndex of all element
        var prevTabIndex = tabIndexList.find(function(element){return element<currentTabIndex;});
        for(var i=selectables.length-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === prevTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
	}

	/**
	 * :focusable and :tabbable, both taken from jQuery UI Core
	 */
	$.extend($.expr[ ':' ], {
		data: $.expr.createPseudo ?
			$.expr.createPseudo(function(dataName){
				return function(elem){
					return !!$.data(elem, dataName);
				};
			}) :
			// support: jQuery <1.8
			function(elem, i, match){
				return !!$.data(elem, match[ 3 ]);
			},

		focusable: function(element){
			return focusable(element, !isNaN($.attr(element, 'tabindex')));
		},

		tabbable: function(element){
			var tabIndex = $.attr(element, 'tabindex'),
				isTabIndexNaN = isNaN(tabIndex);
			return ( isTabIndexNaN || tabIndex >= 0 ) && focusable(element, !isTabIndexNaN);
		}
	});

	/**
	 * focussable function, taken from jQuery UI Core
	 * @param element
	 * @returns {*}
	 */
	function focusable(element){
		var map, mapName, img,
			nodeName = element.nodeName.toLowerCase(),
			isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex'));
		if('area' === nodeName){
			map = element.parentNode;
			mapName = map.name;
			if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map'){
				return false;
			}
			img = $('img[usemap=#' + mapName + ']')[0];
			return !!img && visible(img);
		}
		return ( /^(input|select|textarea|button|object)$/.test(nodeName) ?
			!element.disabled :
			'a' === nodeName ?
				element.href || isTabIndexNotNaN :
				isTabIndexNotNaN) &&
			// the element and all of its ancestors must be visible
			visible(element);

		function visible(element){
			return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function(){
				return $.css(this, 'visibility') === 'hidden';
			}).length;
		}
	}
})(jQuery);
</script>

<a tabindex="5">5</a><br>
<a tabindex="20">20</a><br>
<a tabindex="3">3</a><br>
<a tabindex="7">7</a><br>
<a tabindex="20">20</a><br>
<a tabindex="0">0</a><br>

<script>
var timer;
function tab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabNext();}, 1000);
}
function shiftTab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabPrev();}, 1000);
}
</script>
<button tabindex="-1" onclick="tab()">Tab</button>
<button tabindex="-1" onclick="shiftTab()">Shift+Tab</button>

</body>
</html>

ฉันแก้ไขjquery.tabbable PlugIn ให้เสร็จสมบูรณ์


คำตอบนี้ซ้ำซึ่งโพสต์โดยผู้สร้างปลั๊กอิน jQuery นั้น
mbomb007

0

เนโครแมนซิ่ง.
ฉันมี 0-tabIndexes จำนวนหนึ่งซึ่งฉันต้องการนำทางด้วยแป้นพิมพ์
เนื่องจากในกรณีนั้นมีเพียง ORDER ขององค์ประกอบเท่านั้นที่มีความสำคัญฉันจึงใช้มันdocument.createTreeWalker

ก่อนอื่นให้สร้างตัวกรอง (คุณต้องการเฉพาะองค์ประกอบ [มองเห็นได้] ซึ่งมีแอตทริบิวต์ "tabIndex" ที่มีค่าเป็นตัวเลข

จากนั้นคุณตั้งค่าโหนดรูทซึ่งคุณไม่ต้องการค้นหา ในกรณีของฉันthis.m_treeคือ ul-element ที่มีต้นไม้ที่สลับได้ หากคุณต้องการเอกสารทั้งหมดแทนเพียงแทนที่ด้วยthis.m_treedocument.documentElement

จากนั้นคุณตั้งค่าโหนดปัจจุบันเป็นองค์ประกอบที่ใช้งานอยู่ปัจจุบัน:

ni.currentNode = el; // el = document.activeElement

แล้วคุณจะกลับมาหรือni.nextNode()ni.previousNode()

หมายเหตุ:
สิ่งนี้จะไม่ส่งคืนแท็บตามลำดับที่ถูกต้องหากคุณมี tabIndices! = 0 และลำดับองค์ประกอบไม่ใช่ลำดับ tabIndex ในกรณีของ tabIndex = 0 tabOrder จะเป็นลำดับองค์ประกอบเสมอซึ่งเป็นเหตุผลว่าทำไมจึงใช้งานได้ (ในกรณีนั้น)

protected createFilter(fn?: (node: Node) => number): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        return NodeFilter.FILTER_ACCEPT;
    }

    if (fn == null)
        fn = acceptNode;


    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter: NodeFilter = <NodeFilter><any>fn;
    (<any>safeFilter).acceptNode = fn;

    return safeFilter;
}



protected createTabbingFilter(): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        if (!node)
            return NodeFilter.FILTER_REJECT;

        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;

        if (window.getComputedStyle(<Element>node).display === "none")
            return NodeFilter.FILTER_REJECT;

        // "tabIndex": "0"
        if (!(<Element>node).hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;

        let tabIndex = parseInt((<Element>node).getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;

        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;

        return NodeFilter.FILTER_ACCEPT;
    }

    return this.createFilter(acceptNode);
}


protected getNextTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker

    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);

    ni.currentNode = el;

    while (currentNode = ni.nextNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}


protected getPreviousTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);
    ni.currentNode = el;

    while (currentNode = ni.previousNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}

โปรดสังเกตว่า while-loop

while (currentNode = ni.nextNode())
{
    // Additional checks here
    // if(condition) return currentNode;
    // else the loop continues;
    return <HTMLElement>currentNode; // everything is already filtered down to what we need here
}

จะมีเฉพาะในกรณีที่คุณต้องการหากคุณมีเกณฑ์เพิ่มเติมซึ่งคุณไม่สามารถกรองในตัวกรองที่ส่งผ่านไปยัง createTreeWalker

โปรดทราบว่านี่คือ TypeScript คุณต้องลบโทเค็นทั้งหมดที่อยู่หลังเครื่องหมายโคลอน (:) และระหว่างวงเล็บเหลี่ยม (<>) เช่น<Element>หรือ:(node: Node) => numberเพื่อรับ JavaScript ที่ถูกต้อง

นี่เป็นบริการ JS ที่โปร่งใส:

"use strict";
function createFilter(fn) {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        return NodeFilter.FILTER_ACCEPT;
    }
    if (fn == null)
        fn = acceptNode;
    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter = fn;
    safeFilter.acceptNode = fn;
    return safeFilter;
}
function createTabbingFilter() {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        if (!node)
            return NodeFilter.FILTER_REJECT;
        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;
        if (window.getComputedStyle(node).display === "none")
            return NodeFilter.FILTER_REJECT;
        // "tabIndex": "0"
        if (!node.hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;
        let tabIndex = parseInt(node.getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;
        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;
        return NodeFilter.FILTER_ACCEPT;
    }
    return createFilter(acceptNode);
}
function getNextTab(el) {
    let currentNode;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker
    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.nextNode()) {
        return currentNode;
    }
    return el;
}
function getPreviousTab(el) {
    let currentNode;
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.previousNode()) {
        return currentNode;
    }
    return el;
}

0

ฉันใช้รหัสนี้

    $(document).on('change', 'select', function () {
    let next_select = $(this);
// console.log(next_select.toArray())
    if (!next_select.parent().parent().next().find('select').length) {
        next_select.parent().parent().parent().next().find('input[type="text"]').click()
        console.log(next_select.parent().parent().parent().next());
    } else if (next_select.parent().parent().next().find('select').prop("disabled")) {
        setTimeout(function () {
            next_select.parent().parent().next().find('select').select2('open')
        }, 1000)
        console.log('b');
    } else if (next_select.parent().parent().next().find('select').length) {
        next_select.parent().parent().next().find('select').select2('open')
        console.log('c');
    }
});

สวัสดีมันจะดีมากถ้าคุณอธิบายโค้ดสักหน่อยมันจะช่วยให้ผู้อ่านใหม่เข้าใจได้ดีขึ้น ขอบคุณ
Sunil Lulla

0

อ้างอิงไลบรารี js npm ที่บริสุทธิ์สำหรับกลยุทธ์การนำทางแท็บดังกล่าว
keyboard-navigator
ไลบรารีขนาดเล็กนี้จัดการกับปุ่มแท็บการนำทางด้วยปุ่มลูกศรการรักษาโฟกัสที่การอัปเดต dom การดักโฟกัสแบบโมดอลโฟกัสทางเลือก


0

คำของคำแนะนำ: อย่าพยายามที่จะควบคุมที่มุ่งเน้นที่ดินในช่วงเหตุการณ์แท็บ แต่พยายามที่จะควบคุมซึ่งองค์ประกอบและไม่ได้ tabbable โดยการตั้งค่าtabIndexขององค์ประกอบที่คุณไม่-1ต้องการที่จะได้รับการโฟกัสไปยัง เช่น

// `tabContainer` is a container where we want only
// element at a time to be tabbable, e.g. a radio menu.

tabContainer.addEventListener("focusin", () => {
  const desired = findDesiredFocusElement();

  if (!desired) {
    // Just leave the focus be. We have no preference
    // at the moment.
    return;
  }

  // Move the focus to the correct element.
  desired.focus();

  // Remove all undesired elements from the tab order.
  for (const undesired of findUndesiredFocusElements()) {
    // Make it untabbable.
    undesired.tabIndex = -1;
  }
});

tabContainer.addEventListener("focusout", (event) => {
  for (const element of findRelevantFocusElements()) {
    // Give each element back their focus capability.
    element.tabIndex = 0;
  }
});

หมายเหตุ:นี่อาจไม่ใช่สิ่งที่ดีที่สุดในสถานการณ์ของคุณเช่นในกรณีของคุณการควบคุมดัชนีแท็บในบางchangeเหตุการณ์อาจจะดีกว่าหรือไม่รีเซ็ตtabIndexสถานะfocusoutเป็นต้น

ข้อมูลเพิ่มเติมที่นี่


-1

คุณระบุค่า tabIndex ของคุณเองสำหรับแต่ละองค์ประกอบที่คุณต้องการหมุนเวียนหรือไม่ ถ้าเป็นเช่นนั้นคุณสามารถลองสิ่งนี้:

var lasTabIndex = 10; //Set this to the highest tabIndex you have
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    var curIndex = $(currentElement).attr('tabindex'); //get the tab index of the current element
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    $('[tabindex=' + (curIndex + 1) + ']').focus(); //set focus on the element that has a tab index one greater than the current tab index
}

คุณใช้ jquery ใช่ไหม?


เราไม่ได้ใช้ JQuery เนื่องจากมันทำลายแอปพลิเคชัน : /
JadziaMD

โอเคฉันคิดว่าฉันสามารถเขียน
ซ้ำได้

ทุกองค์ประกอบที่เราสนใจจะมีการกำหนดค่าดัชนีแท็บไว้
JadziaMD

-1

ฉันตรวจสอบวิธีแก้ปัญหาข้างต้นแล้วและพบว่าค่อนข้างยาว สามารถทำได้ด้วยรหัสเพียงบรรทัดเดียว:

currentElement.nextElementSibling.focus();

หรือ

currentElement.previousElementSibling.focus();

ที่นี่ currentElement อาจเป็น document.activeElement หรือถ้าองค์ประกอบปัจจุบันอยู่ในบริบทของฟังก์ชัน

ฉันติดตามเหตุการณ์แท็บและแท็บกะด้วยเหตุการณ์คีย์ดาวน์

let cursorDirection = ''
$(document).keydown(function (e) {
    let key = e.which || e.keyCode;
    if (e.shiftKey) {
        //does not matter if user has pressed tab key or not.
        //If it matters for you then compare it with 9
        cursorDirection = 'prev';
    }
    else if (key == 9) {
        //if tab key is pressed then move next.
        cursorDirection = 'next';
    }
    else {
        cursorDirection == '';
    }
});

เมื่อคุณมีทิศทางเคอร์เซอร์แล้วคุณสามารถใช้nextElementSibling.focusหรือpreviousElementSibling.focusวิธีการ


1
น่าเสียดายที่ลำดับพี่น้องไม่เกี่ยวข้องกับลำดับแท็บยกเว้นด้วยเหตุบังเอิญที่น่ายินดีและไม่มีการรับประกันว่าพี่น้องก่อนหน้า / ถัดไปจะโฟกัสได้
Lawrence Dol
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.