
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 sort
değ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!