HTML 문서 안에서 <A> , <INPUT> , <SELECT> , <TEXTAREA> 등의 태그는 키보드나 마우스 입력을 통해 focus를 가지며, 키보드의 tab 키를 눌러 다음 항목으로, shift + tab 을 눌러 이전 항목으로 focus를 이동할 수 있다.
항목(태그)에 tabindex 속성이 주어지지 않았을 경우, 일반적으로, HTML에 코딩된 순서대로 위에서 아래로, 왼쪽에서 오른쪽으로 focus가 이동을 한다.
위와 같은 화면에서는 왼쪽에서 오른쪽으로 tab 이 이동하는데, 위에서 아래로 먼저 이동하도록 설정하고 싶을 수 있을 것이다. 그럴 때는 tabindex="1" 과 같이 각 태그에 부여하면 이 값의 순서에 따라 focus 순서가 재배치 된다.
하지만, 문서 전체에 아주 많은 항목들이 있고, 그 중 일부의 항목에 대해서만 tab index 를 변경하려면, 그 문서 내에 모든 항목에 전부 tabindex 속성을 지정해야한다. 이는 너무 비효율적이다.
변경하지 않을 항목에 tabindex 속성을 생략하면, 생략된 항목들은 tabindex 속성이 있는 항목들보다 나중 순서에 focus를 갖게 된다. 즉, tabindex 가 부여된 항목을 모두 지난 후, tabindex 가 없는 항목을 다시 지나가게 된다. 이것은 의도하지 않은 결과이다.
현재로서는 Javascript의 도움을 받지 않고 HTML 내에서 일부의 항목만 변경된 index를 갖는 방법은 없는 듯 하다.
How to create tabindex groups?
이 문서의 솔루션을 참고하여 새로 코드를 작성하였다. 일단 jquery 와 underscore 라이브러리가 필요하다.
1 |
<input type="text" data-tabgroup="section1" data-tabgroupindex="2" /> |
data-tabgroup 속성으로 변경된 tab 순서를 가질 항목들을 제한하고, data-tabgroupindex 속성으로 순서를 재정의한다. 이 값을 가지는 항목(태그)들은 위 아래 tabindex 속성을 가지지 않은 다른 태그들과 잘 어울려 의도한 대로 tab 순서를 갖게 된다.
윈 소스와 다른 점은,
- tab index가 같은 tabgroup 항목들 안에서만 이동하지 않고, 위 아래의 (tab index 가 지정되지 않은) 다른 항목들로 자연스럽게 이동.
-
tabgroup 에 처음 진입할 때, 해당 항목의 index 가 첫번째가 아니어도 지정된 index 로 이동.
- 마우스로 focus 를 얻을 때는 동작하지 않도록 (키보드로만 동작하도록) 개선 필요.
- 문서 내에 <form> 이 여러 개 존재하더라도 관계없이 동작.
Source Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
<!doctype html> <html lang="en"> <head> <script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script type="text/javascript" src="http://underscorejs.org/underscore-min.js"></script> <style> label { width: 80px; display: inline-block; } div.label { width: 80px; float: left; } input { width: 80px; } textarea { width: 80px; } div.row { margin: 5px; clear: both; } </style> </head> <body> <form> <h3>Section 1</h3> <div> <div class="row"> <label for="field11">Field 11</label> <input type="text" id="field11" name="field11" /> </div> <div class="row"> <label for="field12">Field 12</label> <input type="text" id="field12" name="field12" data-tabgroup="section1" data-tabgroupindex="3" placeholder="tabindex: 3" /> </div> <div class="row"> <label for="field13">Field 13</label> <a href="#" id="field13" data-tabgroup="section1" data-tabgroupindex="5" >Link13 (tabindex: 5)</a> </div> <div class="row"> <label for="field14">Field 14</label> <input type="text" id="field14" name="field14" data-tabgroup="section1" data-tabgroupindex="2" placeholder="tabindex: 2" /> </div> <div class="row"> <label for="field15">Field 15</label> <input type="text" id="field15" name="field15" data-tabgroup="section1" data-tabgroupindex="4" placeholder="tabindex: 4" /> </div> </div> </form> <form> <h3>Section 2</h3> <div> <div class="row"> <label for="field1">Field 21</label> <input type="text" id="field21" name="field21" /> </div> <div class="row"> <label for="field2">Field 22</label> <input type="text" id="field22" name="field22" /> </div> <div class="row"> <div class="label"> </div> <div class="label">Field 23</div> <div class="label">Field 24</div> <div class="label">Field 25</div> </div> <div class="row"> <label>Min</label> <input type="text" size="5" data-tabgroup="section2" data-tabgroupindex="1" placeholder="tabindex: 1" /> <input type="text" size="5" data-tabgroup="section2" data-tabgroupindex="3" placeholder="tabindex: 3" /> <input type="text" size="5" data-tabgroup="section2" data-tabgroupindex="5" placeholder="tabindex: 5" /> </div> <div class="row"> <label>Max</label> <input type="text" size="5" data-tabgroup="section2" data-tabgroupindex="2" placeholder="tabindex: 2" /> <input type="text" size="5" data-tabgroup="section2" data-tabgroupindex="4" placeholder="tabindex: 4" /> <input type="text" size="5" data-tabgroup="section2" data-tabgroupindex="6" placeholder="tabindex: 6" /> </div> </div> </form> <form> <h3 class="header">Section 3</h3> <div> <div class="row"> <label for="field31">Field31</label> <input type="text" id="field31" name="field31" data-tabgroup="section3" data-tabgroupindex="1" placeholder="tabindex: 1" /> </div> <div class="row"> <label for="field32">Field32</label> <textarea rows="3" id="field32" name="field32" data-tabgroup="section3" data-tabgroupindex="3" placeholder="tabindex: 3"></textarea> </div> <div class="row"> <label for="field33">Field32</label> <select id="field33" name="field33" data-tabgroup="section3" data-tabgroupindex="2"> <option>option 1</option> <option>option 2</option> <option>option 3</option> </select> </div> <div class="row"> <label for="field34">Field34</label> <textarea rows="3" id="field34" name="field34" data-tabgroup="section3" data-tabgroupindex="4" placeholder="tabindex: 4"></textarea> </div> </div> </form> <script> $(document).on('focus', '[data-tabgroup]', function(e) { // TODO : 키보드로 들어올 때만 체크 필요 var node = $(e.target); var nodeIndex = node.data("tabgroupindex"); var tabgroup = node.data("tabgroup"); var tabgroupNodes = $("[data-tabgroup='" + tabgroup + "']"); var tabgroupIndexes = []; _.each(tabgroupNodes, function(item) { tabgroupIndexes.push(+$(item).data("tabgroupindex")); }); tabgroupIndexes = _(tabgroupIndexes).compact(); var orderedTabgroupIndexes = _.sortBy(tabgroupIndexes, function(num) { return num; }); var lastNodeIndex = tabgroupIndexes.length - 1; //console.log('nodeIndex: ' + nodeIndex); //console.log('tabgroupIndexes[0]: ' + tabgroupIndexes[0]); //console.log('orderedTabgroupIndexes[0]: ' + orderedTabgroupIndexes[0]); //console.log('$(e.relatedTarget).data(tabgroup): ' + $(e.relatedTarget).data('tabgroup')); // tabgroup 의 첫번째 객체이긴 하지만, 가장 빠른 tabgroupindex 가 아니고, 외부에서 진입했을 때 if ( (nodeIndex == tabgroupIndexes[0]) && (nodeIndex != orderedTabgroupIndexes[0]) && ($(e.relatedTarget).data('tabgroup') != tabgroup) ) { $("[data-tabgroup='" + tabgroup + "'][data-tabgroupindex='" + orderedTabgroupIndexes[0] + "']").focus(); } // tabgroup 의 마지막 객체이긴 하지만, 가장 나중 tabgroupindex 가 아니고, 외부에서 진입했을 때 else if ( (nodeIndex == tabgroupIndexes[lastNodeIndex]) && (nodeIndex != orderedTabgroupIndexes[orderedTabgroupIndexes.length - 1]) && ($(e.relatedTarget).data('tabgroup') != tabgroup) ) { $("[data-tabgroup='" + tabgroup + "'][data-tabgroupindex='" + orderedTabgroupIndexes[lastNodeIndex] + "']").focus(); } e.preventDefault(); }); $(document).on('keydown', '[data-tabgroup]', function(e) { if (e.which === 9) { var node = $(e.target); var nodeIndex = node.data("tabgroupindex"); var tabgroup = node.data("tabgroup"); var allNodes = $(document).find("input,a,textarea,select"); var tabgroupNodes = $("[data-tabgroup='" + tabgroup + "']"); var tabgroupIndexes = []; _.each(tabgroupNodes, function(item) { tabgroupIndexes.push(+$(item).data("tabgroupindex")); }); tabgroupIndexes = _(tabgroupIndexes).compact(); var orderedTabgroupIndexes = _.sortBy(tabgroupIndexes, function(num) { return num; }); var lastNodeIndex = tabgroupIndexes.length - 1; // 숫자 validation if (isNaN(parseFloat(nodeIndex)) || !isFinite(nodeIndex)) { return; } if (e.which === 9) if (e.shiftKey) { // Shift + Tab 키가 입력되었을 경우 (역방향) var prevElement = orderedTabgroupIndexes[orderedTabgroupIndexes.indexOf(nodeIndex) - 1]; if (typeof(prevElement) === "undefined") { // prevElement is not exist var prevIndex = parseInt( $.map( allNodes, function(obj, index) { if ($(obj).data('tabgroup') == tabgroup && $(obj).data('tabgroupindex') == nodeIndex) { return index; } } ) ) - tabgroupIndexes.indexOf(nodeIndex) - 1; allNodes[prevIndex].focus(); } else { $("[data-tabgroup='" + tabgroup + "'][data-tabgroupindex='" + prevElement + "']").focus(); } } else { // Tab 키가 눌렸을 경우 (정방향) var nextElement = orderedTabgroupIndexes[orderedTabgroupIndexes.indexOf(nodeIndex) + 1]; if (typeof(nextElement) === "undefined") { var nextIndex = parseInt( $.map( allNodes, function(obj, index) { if ($(obj).data('tabgroup') == tabgroup && $(obj).data('tabgroupindex') == nodeIndex) { return index; } } ) ) + (lastNodeIndex - tabgroupIndexes.indexOf(nodeIndex)) + 1; // 뒤에 더 이상 객체가 없으면 원래 동작을 수행 (주소창으로 이동) if (typeof(allNodes[nextIndex]) === "undefined") { return; } allNodes[nextIndex].focus(); } else { $("[data-tabgroup='" + tabgroup + "'][data-tabgroupindex='" + nextElement + "']").focus(); } } e.preventDefault(); } }); </script> </body> </html> |
JSFiddle:
댓글 남기기