Генерация таблицы из JSON
В общем то, решил попробовать написать что-то.
Берем JSON, отображаем его в виде таблицы с постраничной навигацией.. Хотелось бы советов, критики и.т.д. Как можно больше, если, конечно не лень будет смотреть=). class Table{ constructor(config){ this.config = config; this.currentPage = 1; this.init(); } splitByPages(data){ let headers = data.shift(), //заголовок таблицы (id == Идентификатор и.т.д) container = document.querySelector(this.config.output.container), // сюда помещаются все данные pages = [], // матрица страниц numPages = Math.ceil(data.length / this.config.perPage); // подсчет кол-ва необходимых страниц // разбиваем все данные по страницам. // массив имеет следующий вид: pages[номер страницы] === [[], [], [].....] for (let i = 0; i < numPages; i++){ pages[i] = []; for (let j = 0; j < this.config.perPage; j++){ if (data.length === 0) break; pages[i].push(data.shift()); } } // записываем информацию о сраницах в localStorage localStorage.setItem("pages", JSON.stringify(pages)); // создаем начальную сруктуру таблицы let table = document.createElement('table'), thead = document.createElement('thead'), tr = document.createElement('tr'); // генерируем заголовок таблицы for (let item of headers){ let td = document.createElement('td'); td.setAttribute('data-field', item.field); td.innerHTML = item.title; tr.appendChild(td); } thead.appendChild(tr); table.appendChild(thead); container.appendChild(table); } // переключение между страницами (т.е генерация таблицы на основе массива pages из localStorage) switchPage(){ // если данных в localStorage по какой-то причине нет, то останавливаем работу метода. if (!localStorage.getItem('pages')) return; let table = document.querySelector(this.config.output.container + ' table'), // таблица pages = JSON.parse(localStorage.getItem('pages')), // массив со страницами tbody = document.querySelector(this.config.output.container + ' table tbody'); // элемент tbody в таблице // если tbody не найден, то создаем его. В обратном случае обнуляем. if (!tbody){ tbody = document.createElement('tbody'); } else { tbody.innerHTML = ''; } // генерируем содержимое элемента tbody pages[this.currentPage - 1].forEach((row)=>{ let tr = document.createElement('tr'); row.forEach((cell)=>{ let td = document.createElement('td'); td.innerHTML = cell; tr.appendChild(td); }); tbody.appendChild(tr); }); table.appendChild(tbody); } pagination(){ // если данных в localStorage по какой-то причине нет, то останавливаем работу метода. if (!localStorage.getItem('pages')) return; let numPages = JSON.parse(localStorage.getItem('pages')).length, // кол-во страниц container = document.querySelector(this.config.output.container), // сюда помещаются все данные items = document.createElement('ul'); // контейнер пунктов меню // генерируем необходимое кол-во элементов. for (let i = 0; i < numPages; i++){ let item = document.createElement('li'); item.innerHTML = i+1; items.appendChild(item); } // вешаем обработчик клика на элементы пагинации items.addEventListener('click', (e)=>{ if (e.target.tagName !== 'LI') return false; this.currentPage = +e.target.innerHTML; // обновляем текущую страницу this.switchPage(); // перерисовываем таблицу (tbody) }); // добавляем пагинацию в контейнер container.appendChild(items); } init(){ // запрос к серверу fetch(this.config.url) .then((response)=>{ if (response.status !== 200){ console.error('Something went wrong, response status: '+response.status); return; } response.json().then((response)=>{ this.splitByPages(response); // разделяем данные по страницам this.switchPage(); // так как изначально this.currentPage = 1, то отрисовываем первую страницу this.pagination(); // генерируем пагинацию. }); }) .catch((err)=>{ console.error(err); }); } } Вызывается таким способом. <div class="dataContainer data"></div> <script> new Table({ url: 'http://thethz.com/dataset.php?type=small', perPage: 50, output:{ container: '.dataContainer' } }); </script> Многое не сделано (активный пункт навигации, нормальная навигация по страницам, т.е, если много пунктов, то они все отображаются, но это не главное). А вот так вот это дело работает: http://jsfiddle.net/1sbLt00y/ |
Цитата:
|
Методы длинные, хороший метод - 3-4 строки, дробите мельче)
|
Цитата:
|
Цитата:
|
Почему вдруг splitByPages создает header таблицы? Название метода должно отображать его суть и содержимое не должно выходить за рамки.
У вас должны быть отдельные методы назначения обработчиков, снятия обработчиков. Методы отвечающие за создание дома хоршо бы тоже сделать отдельно. |
Где jsDoc? :)
|
Цитата:
|
я бы писал что-то типа такого (отрывок):
createPagination() { this.createPaginationDom(); this.setPaginationHandlers(); } setPage(index) { if (this.hasPage(index)) { this.currentPage = index; this.refrashBody(); } } switchPage - заменил бы на setPage и дать ей на вход номер страницы, тогда этот метод будет заодно служить публичным АПИ по переключению страницы из js |
tysonfury2015, нет, я хочу лишь показать то, что я хреновый, и спросить людей, как стать лучше, а ты, друг, можешь идти туда, откуда вылез, со своим трешом.
|
|
tsigel, да,спасибо,я думал об этом.
|
рони, дело не в плагине, дело в велосипеде=) Мне это вообще не нужно, лишь для обучения.
|
Цитата:
|
tysonfury2015,
Уважаемый, будьте вежливее. Вас никто тут не унижает и не оскорбляет, прошу и вас так же поступать к другим участникам форума. |
devote,
Я не выдержал просто. Зачем тут держать этих клоунов? Они бесят меня по жизни. Он пару дней назад не мог понять, что такое область видимости, с асинхронными запросами запутался в 2-х соснах, хотел чтобы у него из синхронного вызова result=asyncQuery() вернулся результат. Посмотрите его темы. там такой тупняк, что финиш просто. Ниже его уровня -- некуда. Но вворачивать умные словечки в свой поток бреда он не забывает, однако. И вот не прошло и тысячи лет, как он тут пишет fetch(this.config.url) .then((response)=>{ if (response.status !== 200){ console.error('Something went wrong, response status: '+response.status); return; } response.json().then((response)=>{ this.splitByPages(response); // разделяем данные по страницам this.switchPage(); // так как изначально this.currentPage = 1, то отрисовываем первую страницу this.pagination(); // генерируем пагинацию. }); }) .catch((err)=>{ console.error(err); }); Нахрена это нужно на форуме? Тут что цирк чтоли? Если бы он пришел и сказал: "поцаны, помогите разобраться, че это за фигня, как это все называется, что тут вообще происходит, где почитать букварь для первоклассника" -- я бы и слова не сказал. Это еще, спасибо, что он про моноиды в категориях эндофункторов пока не прочитал. Нахрен это нужно? Почему это существо еще не в бане? Вы хотите превратить форум в цирк? |
tysonfury2015, может я в чем то заблуждаюсь, но, форум для того и нужен, чтоб общаться, делиться опытом. И да, такие существа как вы тоже нужны, ибо без вас будет все тускло и однообразно. И да, почему ко мне столько внимания, фанат?? А на счет асинхронности, да, было дело, я этого не знал, но теперь знаю, и чья это заслуга? Людей, которые мне объяснили, ибо я понятия не имел что и как там происходит. А существа, как "Вы" ничего из себя не представляют, лишь срут в темах. И уж поверьте, я стараюсь усвоить все, что мне советуют. А если я написал сценарий для цирка, над которым нужно смеяться, то да, можете посмеяться и пройти мимо если нечего сказать по делу, как и поступает 30% сообщества, другие 50%, если есть возможность и желание, они помогут разобраться, а оставшиеся 20%, те, в которые входите вы, они срут в темах, а по делу ни слова. Пустые слова.
|
Цитата:
|
Цитата:
Цитата:
Цитата:
Цитата:
Цитата:
Цитата:
|
Цитата:
|
tysonfury2015, надеюсь, когда нибудь ты отойдешь от той "хрени", которую ты употребляешь, ибо такую ересь можно "нести" только в нетрезвом состоянии.
По делу: tsigel, попробовал учесть все ваши замечания, кроме методов в 3-4 строки =). Ну и немного изменил хранилище данных (точнее его наполнение), ибо столкнулся с проблемой его переполнения. class Tysonfury2015_idi_mimo{ /** * @constructor * @param {Object} config{url: 'path/to/json', perPage: Integer, output: { container: 'element class/id'}} */ constructor(config){ this.config = config; this.init(); this.storageToken = 'Data__' + Math.round(new Date().getTime() / 1000); } /** * Получет данные в формате JSON по адресу this.config.url * * @param {Callback} */ getData(success){ fetch(this.config.url) .then((response)=>{ if (response.status !== 200){ console.error('Response error! Status - '+response.status); return; } response.json().then((response)=>{ success(response); }); }) .catch((error)=>{ console.error(error); }); } /** * Рабиваем массив с данными по страницам * * @param {Array} */ splitDataByPages(data){ let pages = [], header = data.shift(), numPages = Math.ceil(data.length / this.config.perPage); for (let i = 0; i < numPages; i++){ pages[i] = []; for(let j = 0; j < this.config.perPage; j++){ if (data.length === 0) break; pages[i].push(data.shift()); } } this.putDataToStorage({header, pages}); } /** * Добавляет данные в sessionStorage * * @param data */ putDataToStorage(data){ sessionStorage.setItem(this.storageToken, JSON.stringify(data)); } /** * Возвращает данные из sessionStorage * * @return data */ getDataFromStorage(){ return JSON.parse(sessionStorage.getItem(this.storageToken)); } /** * Удаляет все данные из sessionStorage , которые относятся к этому классу */ clearStorage(){ Object.keys(sessionStorage) .filter((key)=>{ return /^Data__\d+$/.test(key); }) .forEach((key)=>{ sessionStorage.removeItem(key); }); } /** * Генерирует основную структуру таблицы * Таблица (table), заголовок(thead) */ makeTable(){ let table = document.createElement('table'), thead = document.createElement('thead'), tr = document.createElement('tr'), header = this.getDataFromStorage().header; for (let item of header){ let td = document.createElement('td'); td.innerHTML = item.title; tr.appendChild(td); } thead.appendChild(tr); table.appendChild(thead); this.table = table; } /** * Генерирует таблицу (tbody) по странице num * * @param {Number} num - текущая страница */ setPage(num = 1){ num = num < 1 || typeof num !== 'number' ? 1 : num >= this.getDataFromStorage().pages.length ? this.getDataFromStorage().pages.length : num; let page = this.getDataFromStorage().pages[num - 1], tbody = document.querySelector(this.config.output.container+' table tbody'); if (!tbody){ tbody = document.createElement('tbody'); } else { tbody.innerHTML = ''; } page.forEach((row)=>{ let tr = document.createElement('tr'); row.forEach((cell)=>{ let td = document.createElement('td'); td.innerHTML = cell; tr.appendChild(td); }); tbody.appendChild(tr); }); this.table.appendChild(tbody); } /** * Генерирует структуру постраничной навигации */ makePagination(){ let numPages = this.getDataFromStorage().pages.length; if (numPages <= 1) return; let items = document.createElement('ul'); for (let i = 0; i < numPages; i++){ let item = document.createElement('li'); item.innerHTML = i+1; items.appendChild(item); } items.firstChild.classList.add('active'); this.pagination = items; } /** * Устанавливает слушателей событий */ setListeners(){ if (this.pagination){ this.pagination.addEventListener('click', (e)=>{ if (e.target.tagName !== 'LI' || e.target.classList.contains('active')) return false; document.querySelector(this.config.output.container+' ul .active').classList.remove('active'); e.target.classList.add('active'); this.setPage(+e.target.innerHTML); }); } } /** * Последовательно вызывает методоы необходимые для генерации, обработки таблицы и навигации по ней */ init(){ this.clearStorage(); this.getData((data)=>{ this.splitDataByPages(data); this.makeTable(); this.setPage(1); this.makePagination(); let container = document.querySelector(this.config.output.container); container.appendChild(this.table); if (this.pagination){ container.appendChild(this.pagination); } this.setListeners(); }); } } Если я Вас правильно понял и улучшил код, то, если не сложно, подскажите, что еще можно исправить/улучшить? |
Lemme,
Судя по коду вы не используете ide (по крайней мере при reformatCode не осталось бы выравненных присвоений). Это не плохо но с ide удонее. Мне нравится этот гайд по стилю кода. Стало лучше. Мне не ясно зачем тут localStorage? потому как если данные тянутся с сервера то их должно быть не много. И они должны тянуться постронично. На сколько я понимаю это просто для себя. Ок. clearStorage - удалить ключи можно в 1 проход. getData - зачем колбек если у вас есть промис? Возвращайте промис, красиво же! Вы много используете красивых итераторов, но по данным бежите обчсным фором. Почему? В коде должно быть единообразие по максимому. Допустимы сильные отличия в пользу производительности там где она нужна. по возможности действия метода должны нести описательный характер, например в setPage вы проверяете валидность индекса, но намного лучше это вынести в отдельный метод это в 10 раз повысит читабельность метода Я на практике убедился что много однострочных методов - это хорошо, и условия тоже хорошо бывыносить. Это позволяет не вникать в скобки и прочее, а сразу понять суть проверки по названию метода. |
Lemme, тебя не смущает, что твоя таблица (Table) работает со страницами?
splitByPages switchPage pagination :) Это из серии car.cookBorsch(); rabbit.fly(); window.swim() |
Цитата:
Цитата:
Цитата:
for (let key in sessionStorage){ if (/^Data__\d+$/.test(key)){ sessionStorage.removeItem(key); } } Цитата:
Цитата:
по возможности действия метода должны нести описательный характер, например в setPage вы проверяете валидность индекса, но намного лучше это вынести в отдельный метод это в 10 раз повысит читабельность метода Я на практике убедился что много однострочных методов - это хорошо, и условия тоже хорошо бывыносить. Это позволяет не вникать в скобки и прочее, а сразу понять суть проверки по названию метода. Возьму на заметку, спасибо. |
Цитата:
|
Цитата:
Цитата:
var reg = /^Data__\d+$/; Object.keys(sessionStorage).forEach((key) => { if (reg.test(key)) { sessionStorage.removeItem(key); } }); Вынесение объявления рег выражения позволит не создавать его при итерации каждого имени. Методы не просто по возможности должны носить описательный характер. Всякому дроблению должен быть конец :) Не знаю правильно или нет, но мне очень нравится разделять методы которые должны выполнять логику от тех что несут реализацию. И по возможности их не мешать. Ну на примере того же setPage (мне нравится typescript пример будет на нем) public setPage(index:number):void { if (this.hasPage(index)) { this.changeCurrentPage(index); this.render(); } } public render():void { this.clear(); this.createBody(); } private changeCurrentPage(index:number):void { this.currentPage = index; } Конечно многие скажут что удалять тело таблицы и потом его создавать - накладная операция, а таблица должна быть быстра, так что тут это не совсем удачный пример, НО метод clear - нужная штука, он может быть переиспользован в удалении таблице или изменении данных от сервера и прочее, и отдельный метод на создание данных тоже нужен. дробление методов позволяет увеличить переиспользуемость написанного кода. |
Lemme,
Оптимизации и красивости кода должны гармонировать, не стоит писать лишний проход по массиву если его можно не писать и код будет красив, но при этом если массив заведомо мал, а метод используется редко и за один проход выглядит не красиво - не надо пытаться свалить все в 1 кучу (ну то есть не надо пытаться отфильтровать и отмапить и отсортировать в 1 проход массив из 5 элементов, профита вы не увидите). |
tsigel, спасибо за помощь=)
|
Lemme,
Перебирая объекты не забывайте про hasOwnProperty Цитата:
getData() { var promise = fetch(this.config.url); promise.catch(...); returm promise.then(...); } init() { this.getData().then((data) => { ... }); } |
Цитата:
|
Lemme,
Дробление может быть очень хорошим, правда бывает и избытычным, но так вроде не плохо ... promise.catch(this.onGetDataError.bind(this)); ... биндить соответстввенно надо если нужен контекст, в противном случае лучше сделать статичный метод. |
В 171 аргумент не нужен, я просто забыл его убрать, когда не было навигации, я так проверял переход между страницам.
num = num < 1 || typeof num !== 'number' ? 1 : num >= this.getDataFromStorage().pages.length ? this.getDataFromStorage().pages.length : num; Почему ничего? Может я в чем то заблуждаюсь, но: function test(num = 1){ var numPages = 10; if (num < 1 || typeof num !== 'number') { num = 1; } else if (num >= numPages){ num = numPages; } return num; } console.log(test(5)) // 5 console.log(test(100)) // 10 console.log(test('smth')) // 1 |
Цитата:
т.е <li><a href="#page=10">10</a></li> В любом случае, спасибо за подсказку =) |
Часовой пояс GMT +3, время: 09:46. |