Web Bileşeninde Tablo Sıralaması ve Sayfalandırma Oluşturma

Raymond Camden

Geçen hafta ben bloglanmış basit bir web bileşeni oluşturma konusundaki ilk deneyimim hakkında. Dediğim gibi, bu duyduğum bir şeydi yıllar ama asla oynamaya doyamadım. İlk makaleyi okursanız, başlamak için çok fazla çalışma gerekmediğini göreceksiniz. Özel bileşenimi tanımlamak için bir oluşturma işlemine veya çerçeveye ihtiyacım yoktu, yalnızca bir JavaScript dosyasına ihtiyacım vardı. Burada düzenli bir okuyucuysanız, aynı demoyu birkaç kez oluşturduğumu biliyorsunuz, sıralama ve sayfalandırmayı destekleyen Ajax yüklü verilerle temel bir tablo. Bir tazeleme olarak, işte önceki makaleler:

Bu makalelerin her birinde bir arka uç hizmetine ulaştım (https://www.raymondcamden.com/.netlify/functions/get-cats) bir dizi kedi döndürdü. Her dizi örneğinin bir adı, yaşı, türü ve cinsiyet değeri vardı. Önceki demolarımın her biri için, verileri basitçe yükleyen ve işleyen bir demo ile başladım. Daha sonra sıralamayı ekledim. Son bir yineleme olarak, sayfalandırmayı ekledim.

Sürüm Bir – Sadece İşleme

Peki bunu bir web bileşeni olarak nasıl oluştururum? Bir JavaScript dosyasıyla başladım, datatable.js. Bileşenin API’si için planım oldukça basitti. Gerekli bir öznitelik, bir API’ye ve çıktı alınacak belirli sütunları belirtmenize izin verecek isteğe bağlı bir özniteliğe işaret eder. İşte en basit kullanım durumu:

<data-table src="https://www.raymondcamden.com/.netlify/functions/get-cats"></data-table>

Ve işte sütunları belirten bir tane:

<data-table src="https://www.raymondcamden.com/.netlify/functions/get-cats" cols="name,age"></data-table>

İlk yinelememde, yalnızca oluşturmaya odaklandım:

class DataTable extends HTMLElement {

    constructor() {
        super();


        if(this.hasAttribute('src')) this.src = this.getAttribute('src');
        // If no source, do nothing
        if(!this.src) return;

        // attributes to do, datakey + cols
        if(this.hasAttribute('cols')) this.cols = this.getAttribute('cols').split(',');


        const shadow = this.attachShadow({
            mode: 'open'
        });
        
        const wrapper = document.createElement('table');
        const thead = document.createElement('thead');
        const tbody = document.createElement('tbody');
        wrapper.append(thead, tbody);
        shadow.appendChild(wrapper);

        const style = document.createElement('style');
        style.textContent = `
table { 
    border-collapse: collapse;
}

td, th {
  padding: 5px;
  border: 1px solid black;
}

th {
    cursor: pointer;
}
    `;

        // Attach the created elements to the shadow dom
        shadow.appendChild(style);

    }

    async load() {
        console.log('load', this.src);
        // error handling needs to be done :|
        let result = await fetch(this.src);
        this.data = await result.json();
        this.render();
    }

    render() {
        console.log('render time', this.data);
        if(!this.cols) this.cols = Object.keys(this.data[0]);

        this.renderHeader();
        this.renderBody();
    }

    renderBody() {

        let result="";
        this.data.forEach(c => {
            let r="<tr>";
            this.cols.forEach(col => {
                r += `<td>${c[col]}</td>`;
            });
            r += '</tr>';
            result += r;
        });

        let tbody = this.shadowRoot.querySelector('tbody');
        tbody.innerHTML = result;

    }

    renderHeader() {

        let header="<tr>";
        this.cols.forEach(col => {
            header += `<th>${col}</th>`;
        });
        let thead = this.shadowRoot.querySelector('thead');
        thead.innerHTML = header;

    }

    static get observedAttributes() { return ['src']; }

    attributeChangedCallback(name, oldValue, newValue) {
        // even though we only listen to src, be sure
        if(name === 'src') {
            this.src = newValue;
            this.load();
        }
    }

}

// Define the new element
customElements.define('data-table', DataTable);

En baştan, kurucum önce nitelikleri kontrol eder ve en azından sahip olduğundan emin olur. src öznitelik ve isteğe bağlı cols. ne zaman ne yapacağımdan tam olarak emin değildim src geçilmedi, ancak genel olarak, web sayfaları güzel bir şekilde ‘bozulur’ ve ben sadece çıkmanın en basit çözüm olduğunu düşündüm.

Daha sonra DOM’umu oluşturmaya başlıyorum, bu durumda baş ve gövdeli bir masa. Kenarlık eklemek için de bir stil sayfası oluşturuyorum.

Tabloyu oluşturma mantığı birkaç yönteme ayrılmıştır. load verileri getirmeyi işler ve bittiğinde render. kırdım render biri başlık için diğeri gövde için olmak üzere iki işlev daha. Biraz ileriyi düşünüyordum ve üstbilgiyi sıralama veya sayfalamada yeniden oluşturmak istemeyeceğimi düşündüm, sadece gövde. Son olarak şunu unutmayın attributeChangedCallback fark eden kolları src değerler ve arayacak load. Bu, “sadece düz html kullanımımda” çalışır ve değiştirmek için JavaScript kullansaydım işe yarardı. src dinamik olarak değer. Bu sürüme buradan göz atın:

Kalemi Gör WC Masa1 tarafından Raymond Camden (@cfjedimaster) üzerinde kod kalemi.

Sürüm İki – Sıralama

Sıralama için birkaç değişiklik yaptım. İlk olarak, renderHeaderşöyle değiştirdim:

renderHeader() {

    let header="<tr>";
    this.cols.forEach(col => {
        header += `<th data-sort="${col}">${col}</th>`;
    });
    let thead = this.shadowRoot.querySelector('thead');
    thead.innerHTML = header;

    this.shadowRoot.querySelectorAll('thead tr th').forEach(t => {
        t.addEventListener('click', this.sort, false);
    });

}

Sıralanacak sütunu tanımlamak için bir veri özniteliği kullanıyorum ve ardından tıklama olaylarını dinlemek için her bir başlık için bir olay dinleyicisi ekliyorum. Sıralama olayım şu şekilde:

async sort(e) {
    let thisSort = e.target.dataset.sort;
    console.log('sort by',thisSort);

    if(this.sortCol && this.sortCol === thisSort) this.sortAsc = !this.sortAsc;
    this.sortCol = thisSort;
    this.data.sort((a, b) => {
        if(a[this.sortCol] < b[this.sortCol]) return this.sortAsc?1:-1;
        if(a[this.sortCol] > b[this.sortCol]) return this.sortAsc?-1:1;
        return 0;
    });
    this.renderBody();  
}

Click olayını tanıyan öğenin data özniteliğini inceleyerek sütuna göre sıralamayı alıyorum. Bundan sonra, normal bir JavaScript sıralama işlevidir ve ben renderBody.

Şimdi, bu noktada, bir sorunla karşılaştım. İçinde sortdeğeri this artık bileşenimin ana kapsamına işaret etmiyor. Neden hiçbir fikrim yoktu. Biraz googling yaptım ve buna rastladım: Bu nedenle, React’teki Class Components içindeki olay işleyicilerini bağlamamız gerekiyor.. Çok benzer bir sorun gibi görünüyordu ve sorunu tam olarak anlayacağıma söz veremesem de, çözüm benim için iyi çalıştı. Yapıcımda bunu sonuna ekledim:

this.sort = this.sort.bind(this);

Ve bir cazibe gibi çalıştı. Güncellenmiş sürümü burada görebilirsiniz:

Kalemi Gör WC Masa1 tarafından Raymond Camden (@cfjedimaster) üzerinde kod kalemi.

Sürüm Üç – Çağrı

Üçüncü ve son sürüm için sayfalama ekledim. Önceki iki baskımda, bileşenimin ‘kökü’ tablo etiketiydi. Navigasyon için düğmeler ekleyeceğim için, onları içerecek yeni bir div oluşturdum. kullanmak aklıma gelmedi ayak ve şimdi biraz olsun isterdim, ama buna razıyım. Yeni DOM öğelerinin yanı sıra gezinme için iki yeni olay işleyicisine sahip güncellenmiş kurucu burada. Kedi dizim çok büyük olmadığı için sayfa boyutunu 5 olarak ayarladım.

constructor() {
    super();

    if(this.hasAttribute('src')) this.src = this.getAttribute('src');
    // If no source, do nothing
    if(!this.src) return;

    // attributes to do, datakey 
    if(this.hasAttribute('cols')) this.cols = this.getAttribute('cols').split(',');

    this.pageSize = 5;
    if(this.hasAttribute('pagesize')) this.pageSize = this.getAttribute('pagesize');

    // helper values for sorting and paging
    this.sortAsc = false;
    this.curPage = 1;

    const shadow = this.attachShadow({
        mode: 'open'
    });

const table = document.createElement('table');
    const thead = document.createElement('thead');
    const tbody = document.createElement('tbody');
    table.append(thead, tbody);

    const nav = document.createElement('div');
    const prevButton = document.createElement('button');
    prevButton.innerHTML = 'Previous';
    const nextButton = document.createElement('button');
    nextButton.innerHTML = 'Next';
    nav.append(prevButton, nextButton);

    shadow.append(table,nav);

    const style = document.createElement('style');
    style.textContent = `
table { 
border-collapse: collapse;
}

td, th {
padding: 5px;
border: 1px solid black;
}

th {
cursor: pointer;
}

div {
padding-top: 10px;
}
`;
    
    // Attach the created elements to the shadow dom
    shadow.appendChild(style);

    // https://www.freecodecamp.org/news/this-is-why-we-need-to-bind-event-handlers-in-class-components-in-react-f7ea1a6f93eb/
    this.sort = this.sort.bind(this);

    this.nextPage = this.nextPage.bind(this);
    this.previousPage = this.previousPage.bind(this);

    nextButton.addEventListener('click', this.nextPage, false);
    prevButton.addEventListener('click', this.previousPage, false);

}

Dikkat ederseniz tekrar ediyorum bind yeni olay işleyicilerimi arayın. Sayfalandırma şu şekilde yapılır:

nextPage() {
    if((this.curPage * this.pageSize) < this.data.length) this.curPage++;
    this.renderBody();
}

previousPage() {
    if(this.curPage > 1) this.curPage--;
    this.renderBody();
}

Ve daha sonra renderBody ile güncellenir filter sadece verilerin “sayfasını” almak için arayın:

renderBody() {

    let result="";
    this.data.filter((row, index) => {
        let start = (this.curPage-1)*this.pageSize;
        let end =this.curPage*this.pageSize;
        if(index >= start && index < end) return true;
    }).forEach(c => {
        let r="<tr>";
        this.cols.forEach(col => {
            r += `<td>${c[col]}</td>`;
        });
        r += '</tr>';
        result += r;
    });

    let tbody = this.shadowRoot.querySelector('tbody');
    tbody.innerHTML = result;

}

Bu sürümü buradan demo yapabilirsiniz:

Kalemi Gör tuvalet masası2 tarafından Raymond Camden (@cfjedimaster) üzerinde kod kalemi.

Ne kaldı?

Yani, burada gerçekten yaptığım tek şey asgariyi oluşturmaktı. İstemci tarafı geliştirme yaptığım sürece, orada çerçeveler vardı. Süper karmaşık veri tabloları. “API’m bir dizi döndürüyor, ancak öğeler adlı bir alt öğede” gibi şeyler için destek eklediğini görebiliyordum. Sayfa boyutunu geçmeyi de bir özellik olarak görebiliyordum. hatta belki bir colLabels başlık etiketlerimi belirtmeme izin veren özellik. Kaptın bu işi. 🙂 Bu yardımcı olduysa veya herhangi bir sorunuz varsa, bana bildirin!

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.