@@ -43,8 +43,10 @@ const HABBITS_KEY = 'HABBITS';
4343// app
4444const app = {
4545 'menu-list' : document . getElementById ( 'sidebar-list' ) ,
46+ 'logo-wrap' : document . getElementById ( 'logo-wrap' ) ,
4647 header : {
4748 title : document . getElementById ( 'header-title' ) ,
49+ deleteBtn : document . getElementById ( 'delete-habbit-btn' ) ,
4850 progressPercent : document . getElementById ( 'progress-percent' ) ,
4951 progressValue : document . getElementById ( 'progress-value' ) ,
5052 } ,
@@ -63,7 +65,8 @@ function loadData() {
6365 const habbitsStr = localStorage . getItem ( HABBITS_KEY ) ;
6466 const habbitsArr = JSON . parse ( habbitsStr ) ;
6567
66- if ( Array . isArray ( habbitsArr ) && habbitsArr . length ) {
68+ // важная проверка.. т.е. при первичной загрузке в localStorage не будет HABBITS_KEY, соответственно придёт null, если так, то далее приложение начнёт работать/отрисовывать согласно существующего/глобального массива habbits.. но потом при взаимодействии (добавлении/удалении), это поменяется, т.е. после первой saveData() логика начнёт строится согласно localStorage
69+ if ( habbitsArr !== null && Array . isArray ( habbitsArr ) ) {
6770 habbits = habbitsArr ;
6871 }
6972}
@@ -97,41 +100,40 @@ function togglePopup() {
97100 }
98101}
99102
103+ // DRY
104+ function addInputErrorListener ( inputElement , errorClass ) {
105+ inputElement . addEventListener ( 'input' , ( ) => {
106+ if ( inputElement . classList . contains ( errorClass ) ) {
107+ inputElement . classList . remove ( errorClass ) ;
108+ }
109+ } ) ;
110+ }
111+
100112// ** render **
101113function rerenderMenu ( activeHabbit ) {
102- for ( const habbit of habbits ) {
103- const existed = document . querySelector ( `[menu-habbit-id="${ habbit . id } "]` ) ; // определение/флаг.. есть уже элемент (создавался) или нет
104-
105- if ( ! existed ) {
106- // если НЕТ.. создание li/элемента sidebar/меню.. привычки (согласно data)
107- const habbitItem = document . createElement ( 'li' ) ;
108- habbitItem . classList . add ( 'sidebar__nav-item' ) ;
109-
110- const habbitBtn = document . createElement ( 'button' ) ;
111- habbitBtn . classList . add ( 'sidebar__nav-btn' ) ;
112- habbitBtn . setAttribute ( 'type' , 'button' ) ;
113- habbitBtn . setAttribute ( 'aria-label' , 'Выбрать привычку' ) ;
114- habbitBtn . setAttribute ( 'title' , habbit . title ) ;
115- habbitBtn . setAttribute ( 'menu-habbit-id' , habbit . id ) ;
116- habbitBtn . innerHTML = `<img class="sidebar__nav-icon" src="./images/${ habbit . icon } .svg" width="${ habbit . width } " height="${ habbit . height } " alt="Иконка: ${ habbit . name } ">` ;
117-
118- habbitBtn . addEventListener ( 'click' , ( ) => rerender ( habbit . id ) ) ; // тонкий момент.. по сути "замыкание" для каждой кнопки/её ID.. потом передача именно его/себя в rerender()
119-
120- // проверка на активность (при создании)
121- if ( activeHabbit . id === habbit . id ) {
122- habbitBtn . classList . add ( 'sidebar__nav-btn_active' ) ;
123- }
114+ app [ 'menu-list' ] . innerHTML = '' ; // предварительная очистка "sidebar" списка (всё будет создаваться заново)
124115
125- habbitItem . append ( habbitBtn ) ;
126- app [ 'menu-list' ] . append ( habbitItem ) ;
127- } else {
128- // если ЕСТЬ.. только обновление "активного" класса
129- if ( activeHabbit . id === habbit . id ) {
130- existed . classList . add ( 'sidebar__nav-btn_active' ) ;
131- } else {
132- existed . classList . remove ( 'sidebar__nav-btn_active' ) ;
133- }
116+ for ( const habbit of habbits ) {
117+ const habbitItem = document . createElement ( 'li' ) ;
118+ habbitItem . classList . add ( 'sidebar__nav-item' ) ;
119+
120+ const habbitBtn = document . createElement ( 'button' ) ;
121+ habbitBtn . classList . add ( 'sidebar__nav-btn' ) ;
122+ habbitBtn . setAttribute ( 'type' , 'button' ) ;
123+ habbitBtn . setAttribute ( 'aria-label' , 'Выбрать привычку' ) ;
124+ habbitBtn . setAttribute ( 'title' , habbit . title ) ;
125+ habbitBtn . setAttribute ( 'menu-habbit-id' , habbit . id ) ;
126+ habbitBtn . innerHTML = `<img class="sidebar__nav-icon" src="./images/${ habbit . icon } .svg" width="${ habbit . width } " height="${ habbit . height } " alt="Иконка: ${ habbit . name } ">` ;
127+
128+ habbitBtn . addEventListener ( 'click' , ( ) => rerender ( habbit . id ) ) ; // тонкий момент.. по сути "замыкание" для каждой кнопки/её ID.. потом передача именно его/себя в rerender()
129+
130+ // проверка на корректность и активность (при создании)
131+ if ( activeHabbit && activeHabbit . id === habbit . id ) {
132+ habbitBtn . classList . add ( 'sidebar__nav-btn_active' ) ;
134133 }
134+
135+ habbitItem . append ( habbitBtn ) ;
136+ app [ 'menu-list' ] . append ( habbitItem ) ;
135137 }
136138}
137139
@@ -196,11 +198,20 @@ function rerenderHabbitDaysContentEl(activeHabbit) {
196198
197199 // организация прослушки инпута/комментария для корректировки класса ошибки/обводки
198200 const commentInput = document . getElementById ( 'comment-input' ) ;
199- commentInput . addEventListener ( 'input' , ( ) => {
200- if ( commentInput . classList . contains ( 'comment-error' ) ) {
201- commentInput . classList . remove ( 'comment-error' ) ;
202- }
203- } ) ;
201+ addInputErrorListener ( commentInput , 'comment-error' ) ;
202+ }
203+
204+ function renderEmptyState ( ) {
205+ app . header . title . textContent = 'Привычек нет' ;
206+ app . header . progressPercent . textContent = '0%' ;
207+ app . header . progressValue . setAttribute ( 'style' , 'width: 0%' ) ;
208+ app . header . deleteBtn . style . display = 'none' ; // скрытие кнопки удаления (при отсутствии привычек)
209+ app [ 'habbit-days' ] . list . innerHTML =
210+ '<li class="habbit-days__item_empty">Нажмите "плюс", чтобы добавить первую!</li>' ;
211+
212+ app [ 'logo-wrap' ] . style . marginBottom = '0' ; // исключение отступа
213+
214+ rerenderMenu ( null ) ;
204215}
205216
206217// поиск/определение "активной" привычки.. запуск отрисовок элементов/переключение активностей
@@ -212,6 +223,8 @@ function rerender(activeHabbitId) {
212223 return ;
213224 }
214225
226+ app [ 'logo-wrap' ] . style . marginBottom = '50px' ; // добавление отступа
227+ app . header . deleteBtn . style . display = 'flex' ; // отображение кнопки "удалить привычку"
215228 document . location . replace ( document . location . pathname + '#' + activeHabbitId ) ; // добавление хеш #id выбранной привычки к/в конец адресной строки/url
216229
217230 rerenderMenu ( activeHabbit ) ;
@@ -235,6 +248,12 @@ function addCommentDay(event) {
235248
236249 habbits = habbits . map ( ( habbit ) => {
237250 if ( habbit . id === globalActiveHabbitId ) {
251+ const target = Number ( habbit . target ) ; // фиксация цели/дней
252+
253+ if ( target === habbit . days . length + 1 ) {
254+ confirm ( `Вы достигли цели! ${ target } дней!` ) ;
255+ }
256+
238257 return { ...habbit , days : habbit . days . concat ( { comment : dayComment } ) } ; // создание новых объектов, на основе старых (с корректировкой поля/массива days)
239258 }
240259 return habbit ;
@@ -334,6 +353,33 @@ function addNewHabbit(event) {
334353 } , 300 ) ; // замедление перерисовки
335354}
336355
356+ function deleteHabbit ( ) {
357+ const activeHabbit = habbits . find (
358+ ( habbit ) => habbit . id === globalActiveHabbitId
359+ ) ;
360+
361+ if ( ! activeHabbit ) {
362+ return ;
363+ }
364+
365+ const isConfirmed = confirm (
366+ `Вы уверены, что хотите удалить привычку "${ activeHabbit . title } "?`
367+ ) ;
368+
369+ if ( ! isConfirmed ) {
370+ return ; // если отмена удаления..
371+ }
372+
373+ habbits = habbits . filter ( ( habbit ) => habbit . id !== globalActiveHabbitId ) ; // исключение активной/удаляемой привычки
374+ saveData ( ) ; // сохранение/обновление данных
375+
376+ if ( habbits . length > 0 ) {
377+ rerender ( habbits [ 0 ] . id ) ; // отрисовка первой оставшейся привычки
378+ } else {
379+ renderEmptyState ( ) ; // если нет привычек, отрисовка "пустого" состояния
380+ }
381+ }
382+
337383// init
338384( ( ) => {
339385 // загрузка данных.. по сути автоматическая/сразу (соответственно через IIFE)
@@ -349,22 +395,15 @@ function addNewHabbit(event) {
349395 } else {
350396 rerender ( habbits [ 0 ] . id ) ; // нет.. принудительная отрисовка/передача первой привычки согласно глобального habbits массива
351397 }
398+ } else {
399+ renderEmptyState ( ) ; // если нет привычек, показ "пустого" состояния
352400 }
353401
354402 // организация прослушек pop-up инпутов для корректировки классов ошибки (т.е. ввод данных, отмена обводки)
355403 const form = app [ 'pop-up' ] . popupElement . querySelector ( '.pop-up__form' ) ;
356404 const titleInput = form [ 'title-habbit' ] ;
357405 const targetInput = form [ 'target-habbit' ] ;
358406
359- titleInput . addEventListener ( 'input' , ( ) => {
360- if ( titleInput . classList . contains ( 'input-error' ) ) {
361- titleInput . classList . remove ( 'input-error' ) ;
362- }
363- } ) ;
364-
365- targetInput . addEventListener ( 'input' , ( ) => {
366- if ( targetInput . classList . contains ( 'input-error' ) ) {
367- targetInput . classList . remove ( 'input-error' ) ;
368- }
369- } ) ;
407+ addInputErrorListener ( titleInput , 'input-error' ) ;
408+ addInputErrorListener ( targetInput , 'input-error' ) ;
370409} ) ( ) ;
0 commit comments