Menggunakan querySelectorAll untuk mengambil turunan langsung

119

Saya bisa melakukan ini:

<div id="myDiv">
   <div class="foo"></div>
</div>
myDiv = getElementById("myDiv");
myDiv.querySelectorAll("#myDiv > .foo");

Artinya, saya berhasil mengambil semua turunan langsung dari myDivelemen yang memiliki kelas .foo.

Masalahnya, saya merasa terganggu karena saya harus menyertakan #myDivin dalam selector, karena saya menjalankan kueri pada myDivelemen (jadi jelas berlebihan).

Saya seharusnya bisa membiarkan #myDivoff, tapi kemudian selector bukan sintaks hukum karena dimulai dengan a >.

Adakah yang tahu cara menulis selector yang hanya mendapatkan turunan langsung dari elemen yang dijalankan oleh selector?

mattsh
sumber
Apakah tidak ada jawaban yang memenuhi kebutuhan Anda? Harap berikan umpan balik atau pilih jawaban.
Randy Hall
@mattsh - Saya menambahkan jawaban (IMO) yang lebih baik untuk kebutuhan Anda, lihatlah!
csuwldcat

Jawaban:

85

Pertanyaan bagus. Pada saat diminta, cara yang diterapkan secara universal untuk melakukan "kueri yang di-rooting kombinator" (sebagaimana John Resig menyebutnya ) tidak ada.

Sekarang pseudo-class : scope telah diperkenalkan. Ini tidak didukung pada Edge atau IE versi [pra-Chrominum], tetapi telah didukung oleh Safari selama beberapa tahun. Dengan menggunakan itu, kode Anda bisa menjadi:

let myDiv = getElementById("myDiv");
myDiv.querySelectorAll(":scope > .foo");

Perhatikan bahwa dalam beberapa kasus Anda juga dapat melewati .querySelectorAlldan menggunakan fitur API DOM model lama yang bagus. Misalnya saja myDiv.querySelectorAll(":scope > *")Anda bisa langsung menulismyDiv.children , misalnya.

Sebaliknya, jika Anda belum dapat mengandalkan :scope, saya tidak dapat memikirkan cara lain untuk menangani situasi Anda tanpa menambahkan lebih banyak logika filter khusus (misalnya, temukan myDiv.getElementsByClassName("foo")siapa .parentNode === myDiv), dan jelas tidak ideal jika Anda mencoba mendukung satu jalur kode yang benar-benar hanya ingin mengambil string pemilih arbitrer sebagai masukan dan daftar kecocokan sebagai keluaran! Tetapi jika seperti saya, Anda akhirnya menanyakan pertanyaan ini hanya karena Anda terjebak berpikir "yang Anda miliki hanyalah palu" jangan lupa ada berbagai alat lain yang ditawarkan DOM juga.

natevw
sumber
3
myDiv.getElementsByClassName("foo")tidak sama dengan myDiv.querySelectorAll("> .foo"), itu lebih seperti myDiv.querySelectorAll(".foo")(yang sebenarnya bekerja dengan cara) di mana ia menemukan semua keturunan .foobukan hanya anak-anak.
BoltClock
Oh, sial! Yang saya maksud: Anda benar. Saya telah memperbarui jawaban saya untuk membuatnya lebih jelas bahwa itu tidak terlalu bagus dalam kasus OP.
natevw
terima kasih untuk tautannya (yang sepertinya menjawabnya dengan pasti), dan terima kasih telah menambahkan terminologi "kueri yang di-rooting kombinator".
mattsh
1
Apa yang oleh John Resig disebut "kueri berakar kombinator", Penyeleksi 4 sekarang menyebutnya sebagai penyeleksi relatif .
BoltClock
@Andrew Scoped stylesheet (yaitu <style scoped>) tidak ada hubungannya dengan :scopepseudo-selector yang dijelaskan dalam jawaban ini.
natevw
124

Adakah yang tahu cara menulis selector yang hanya mendapatkan turunan langsung dari elemen yang dijalankan oleh selector?

Cara yang benar untuk menulis pemilih yang "di-root" ke elemen saat ini adalah dengan menggunakan :scope.

var myDiv = getElementById("myDiv");
var fooEls = myDiv.querySelectorAll(":scope > .foo");

Namun, dukungan browser terbatas dan Anda memerlukan shim jika ingin menggunakannya. Saya membuat scopedQuerySelectorShim untuk tujuan ini.

malas
sumber
3
Perlu disebutkan bahwa :scopespesifikasi saat ini adalah "Draf Kerja" dan karena itu dapat berubah. Kemungkinannya masih akan berfungsi seperti ini jika / ketika diadopsi, tetapi agak dini untuk mengatakan ini adalah "cara yang benar" untuk melakukannya menurut saya.
Zach Lysobey
2
Sepertinya sudah usang untuk sementara
Adrian Moisa
1
3 tahun kemudian dan Anda menyelamatkan gluteus maximus saya!
Mehrad
5
@Adrian Moisa:: scope tidak digunakan lagi dimanapun. Anda mungkin membuatnya tercampur dengan atribut scoped.
BoltClock
6

Berikut adalah metode fleksibel, ditulis dalam vanilla JS, yang memungkinkan Anda menjalankan kueri pemilih CSS hanya pada turunan langsung dari suatu elemen:

var count = 0;
function queryChildren(element, selector) {
  var id = element.id,
      guid = element.id = id || 'query_children_' + count++,
      attr = '#' + guid + ' > ',
      selector = attr + (selector + '').replace(',', ',' + attr, 'g');
  var result = element.parentNode.querySelectorAll(selector);
  if (!id) element.removeAttribute('id');
  return result;
}
csuwldcat.dll
sumber
Saya sarankan Anda menambahkan semacam cap waktu atau penghitung ke id yang Anda hasilkan. Ada kemungkinan bukan nol (meskipun kecil) untuk Math.random().toString(36).substr(2, 10)menghasilkan token yang sama lebih dari sekali.
Frédéric Hamidi
Saya tidak yakin seberapa besar kemungkinan yang diberikan peluang hash berulang sangat kecil, ID hanya diterapkan sementara untuk sepersekian milidetik, dan setiap dupe juga harus menjadi anak dari node induk yang sama - pada dasarnya, the kemungkinan astronomis. Terlepas dari itu, menambahkan penghitung tampaknya baik-baik saja.
csuwldcat
Tidak yakin apa yang Anda maksud any dupe would need to also be a child of the same parent node, idatribut di seluruh dokumen. Anda benar kemungkinan masih cukup kecil, tapi terima kasih telah mengambil jalan yang bagus dan menambahkan penghitung itu :)
Frédéric Hamidi
8
JavaScript tidak dijalankan secara paralel, jadi peluangnya sebenarnya nol.
WGH
1
@WGH Wow, saya tidak percaya, saya benar-benar tercengang saya akan kentut pada hal yang sederhana (saya bekerja di Mozilla dan sering melafalkan fakta ini, harus lebih banyak tidur! LOL)
csuwldcat
5

jika Anda tahu pasti elemennya unik (seperti kasus Anda dengan ID):

myDiv.parentElement.querySelectorAll("#myDiv > .foo");

Untuk solusi yang lebih "global": (gunakan shim matchSelector )

function getDirectChildren(elm, sel){
    var ret = [], i = 0, l = elm.childNodes.length;
    for (var i; i < l; ++i){
        if (elm.childNodes[i].matchesSelector(sel)){
            ret.push(elm.childNodes[i]);
        }
    }
    return ret;
}

di mana elmelemen induk Anda, dan seladalah pemilih Anda. Bisa juga digunakan sebagai prototipe.

Randy Hall
sumber
@lazd itu bukan bagian dari pertanyaan.
Randy Hall
Jawaban Anda bukanlah solusi "global". Ini memiliki batasan khusus yang harus diperhatikan, oleh karena itu komentar saya.
lazd
@lazd yang menjawab pertanyaan yang diajukan. Jadi mengapa tidak memilih? Atau apakah Anda kebetulan berkomentar dalam beberapa detik setelah menolak jawaban lama?
Randy Hall
Solusinya menggunakan matchesSelectoruncrefixed (yang bahkan tidak berfungsi di versi terbaru Chrome), mencemari namespace global (ret tidak dideklarasikan), tidak mengembalikan NodeList seperti yang diharapkan dari querySelectorMethods. Menurut saya ini bukan solusi yang baik, karena itu suara negatifnya.
lazd
@lazd - Pertanyaan asli menggunakan fungsi yang tidak difiksasi dan namespace global. Ini adalah solusi yang sangat bagus untuk pertanyaan yang diberikan. Jika menurut Anda ini bukan solusi yang baik, jangan gunakan, atau sarankan edit. Jika secara fungsional tidak benar atau spam, pertimbangkan untuk memilih downvote.
Randy Hall
2

Solusi berikut berbeda dengan yang diusulkan sejauh ini, dan berhasil untuk saya.

Alasannya adalah Anda memilih semua turunan yang cocok terlebih dahulu, lalu menyaring yang bukan turunan langsung. Seorang anak adalah anak langsung jika tidak memiliki induk yang cocok dengan selektor yang sama.

function queryDirectChildren(parent, selector) {
    const nodes = parent.querySelectorAll(selector);
    const filteredNodes = [].slice.call(nodes).filter(n => 
        n.parentNode.closest(selector) === parent.closest(selector)
    );
    return filteredNodes;
}

HTH!

Melle
sumber
Saya suka ini karena singkatnya, dan kesederhanaan pendekatannya, meskipun kemungkinan besar tidak akan bekerja dengan baik jika dipanggil pada frekuensi tinggi dengan pohon besar dan penyeleksi yang terlalu rakus.
brennanyoung
1

Saya membuat fungsi untuk menangani situasi ini, saya pikir saya akan membagikannya.

getDirectDecendent(elem, selector, all){
    const tempID = randomString(10) //use your randomString function here.
    elem.dataset.tempid = tempID;

    let returnObj;
    if(all)
        returnObj = elem.parentElement.querySelectorAll(`[data-tempid="${tempID}"] > ${selector}`);
    else
        returnObj = elem.parentElement.querySelector(`[data-tempid="${tempID}"] > ${selector}`);

    elem.dataset.tempid = '';
    return returnObj;
}

Intinya apa yang Anda lakukan adalah menghasilkan string acak (fungsi randomString di sini adalah modul npm yang diimpor, tetapi Anda dapat membuatnya sendiri.) Kemudian menggunakan string acak tersebut untuk menjamin bahwa Anda mendapatkan elemen yang Anda harapkan di selektor. Maka Anda bebas menggunakan >setelah itu.

Alasan saya tidak menggunakan atribut id adalah karena atribut id mungkin sudah digunakan dan saya tidak ingin menimpanya.

cpi
sumber
0

Kita bisa dengan mudah mendapatkan semua turunan langsung dari sebuah elemen menggunakan childNodesdan kita bisa memilih leluhur dengan kelas tertentu querySelectorAll, jadi tidak sulit membayangkan kita bisa membuat fungsi baru yang mendapatkan keduanya dan membandingkan keduanya.

HTMLElement.prototype.queryDirectChildren = function(selector){
  var direct = [].slice.call(this.directNodes || []); // Cast to Array
  var queried = [].slice.call(this.querySelectorAll(selector) || []); // Cast to Array
  var both = [];
  // I choose to loop through the direct children because it is guaranteed to be smaller
  for(var i=0; i<direct.length; i++){
    if(queried.indexOf(direct[i])){
      both.push(direct[i]);
    }
  }
  return both;
}

Catatan: Ini akan mengembalikan Array of Nodes, bukan NodeList.

Pemakaian

 document.getElementById("myDiv").queryDirectChildren(".foo");
Dustin Poissant
sumber
0

Saya ingin menambahkan bahwa Anda dapat memperluas kompatibilitas : scope dengan hanya menetapkan atribut sementara ke simpul saat ini.

let node = [...];
let result;

node.setAttribute("foo", "");
result = window.document.querySelectorAll("[foo] > .bar");
// And, of course, you can also use other combinators.
result = window.document.querySelectorAll("[foo] + .bar");
result = window.document.querySelectorAll("[foo] ~ .bar");
node.removeAttribute("foo");
Vasile Alexandru Peşte
sumber
-1

Saya akan pergi dengan

var myFoo = document.querySelectorAll("#myDiv > .foo");
var myDiv = myFoo.parentNode;
Lee Chase
sumber
-2

Saya hanya melakukan ini tanpa mencobanya. Apakah ini akan berhasil?

myDiv = getElementById("myDiv");
myDiv.querySelectorAll(this.id + " > .foo");

Cobalah, mungkin berhasil mungkin tidak. Apolovies, tapi saya tidak menggunakan komputer sekarang untuk mencobanya (menanggapi dari iPhone saya).

Greeso
sumber
3
QSA memiliki cakupan ke elemen yang dipanggil, jadi ini akan mencari elemen lain di dalam myDiv dengan ID yang sama dengan myDiv, lalu turunannya dengan .foo. Anda bisa melakukan sesuatu seperti `document.querySelectorAll ('#' + myDiv.id + '> .foo');
mungkin sampai