В этом уроке мы с вами сделает такое симпатичное приложения для создания списка задач. Как видите, я могу ввести новую задачу. Добавлю еще пару задач. Указать, что например эту задачу я выполнил. А эту могу отредактировать, и сохранить. Также любую задачу могу удалить. И даже если мы сейчас перезагрузим страницу, задачи не исчезнут.
Приступим к созданию такого приложения. Создадим файл. Назовем его tasks.html. Tasks означает задачи. Внутри файла добавим сперва необходимые теги. Изменим язык на ru, и также поменяем название. Теперь приступим к написанию html кода нашего приложения.
Начнем с тега див, внутри которого поместим поле, кнопки и задачи, которые мы будем добавлять. Зададим ему id Todo-app. Todo с английского переводится как Список задач. Первым тегом внутри тега див будет h1, который просто вывеет текст Список Задач перед полем приложения.
Далее, после тега h1 у нас будет идти тег формы. Id у него бует todo-form. Форма будет состоять из поля, куда мы введем текст и кнопки. Начнем с поля. type будет text. Id зададим как todo-input, а в атрибуте placeholder напишем “Добавь новую задачу…”.
Далее набираем тег button, type будет submit. Текст на Кнопке будет Добавить.
Теперь, после тега form, наберем тег ul, т.е. тег списка внутри которого и будут выводится наши задачи. Id у списка будет todo-list. Теги li мы сейчас набирать не будем они будут появляться с помощью JavaScript. Итак. HTML код готов, переходим к CSS.
Начнем с тега боди:
Шрифт будет любой, но без засечек. Display flex. Отцентруем внутрений тег div как по горизонтали, так и по вертикали. Не забудем указать высоту тега body. Поля будут по 0, А фон будет градиент от светло голубого к фиолетовому. Цвет текста будет серый.
ПРоверяем. Переходим тегу div c id todo app
Ширина тега будет 25em. Отступы 1.3 em, фон будет белый, скругление углов будет в 1 em, и добавим тень.
Далее настроим текстовое поле.
Ширину укажем с помощью процентов, используем 63%, отступы .7em, рамка будет такая. Зададим скругление, и поле с права.
Теперь зададим стиль кнопке:
Отступы будут такие. Цвет светло голубой, текст белый, рамки не будет, скгруление будет .2em. При наведение курсора на кнопку стрелка будет превращаться в указательный палец.также зададим переход для свойства background-color в .3s. А менять background-color мы будем при наведении мышкой. При наведении цвет будет меняться на такой.
Теперь стилизуем теги ul и li.
Удалим отступы у тега ul, теперь переходим к li. Отступы будут .5em. Diplay будет flex, justify content будет space between, т.е. будет одинаковое расстояние между внутренними тегами li, а это текст, чекбокс, кнопка удалить и изменить. Также сделаем все внутренние теги выровненными по центры по высоте.
Теперь нам надо сделать так, чтобы вот эти все правила касались только чекбокса и двух кнопок, а текст бул выровнен по левому краю. Для этого надо сперва тегам li задать позиционирование relative, чтобы текст задачи выравнивался относительно тега li.
Далее зададим стиль тегу span где и будет находиться текст задачи, а также тегу input который будет заменять тег span при редактировании задачи. Поймать этот тег можно будет так.
Задаем им абсолютное позиционирование и выравниваем по левому краю.
Осталось задать левое поле тегу чекбокс чтобы он подвинулся сюда.
CSS готов. переходим теперь к JavaScript.
В начале присвоим переменным в качестве значений форму, поле ввода и список ul.
Далее используем метод adEventListener который будет отслеживать отправку формы, которая происходим при нажатии на кнопку submit. Напишем имя переменной формы и в качестве метода укажем addEventListener. Кстати метод addEventListener называют слушателем, так как он буквально переводится как добавить Слушатель событий, и по сути он как бы слушает события. Когда происходит то событие которое он слушает, например отправку формы, то метод, как вы уже знаете, запускает нужную вам функцию. Продолжаем набирать данный метод.
В первом параметре укажем событие submit, затем напишем функцию, объект событие назовем event. Далее в в теле функции мы должны отменить поведение события submit по умолчанию. А по умолчанию, при нажатии на кнопку событие хочет связаться с сервером и перезагрузить страницу. Отменить поведение по умолчанию для события можно с помощью метода preventDefault().
Пишем сперва событие, точку и метод preventDefault(). Теперь при клике на кнопку ничего не произойдет. Далее создадим константу newTask, т.е. в переводе новое задание и присвоим ей текстовое значение поля todoInput. с помощью его свойства value.
Если пользователь ничего не ввел, т.е. константа newTask равна пустой строке, то выведем предупреждение на экран, чтобы пользователь ввел задание. Вывести предупреждение (также его называют модальным окном) можно с помощью метода alert, в параметре которого мы должны написать сообщение, которое хотим вывести. Набираем
(Ключевое слово не пишем)
Если же пользователь что-то написал, то сперва обнулим поле. И дальше мы должны создать функцию, которая будет добавлять новую задачу в список задач, назовем ее addTask, а в параметре передадим переменную newTask. Как вы понимаете, мы сейчас вызвали функцию newTask, но не создали её. Давайте же её создадим.
В теле функции давайте сперва создадим теги li и span и присвоим эти DOM-элементы новым константам. Создать теги можно с помощью метода createElement объекта document. Таким образом мы создаем теги и храним их в оперативной памяти. Как помните из урока по DOM мы также можем хранить эти теги, или правильней говорить узлы в документной фрагменте, но так как узлов, которые мы хотим хранить, у нас не много, то местом для хранения нам подойдет и оперативная память.
Далее добавим текст из параметра newTask между тегов span. Теперь надо сделать тег span дочерним к узлу ли. Сделать это можно с помощью метода appendChild, который добавляет дочерний узел. Сперва мы пишем константу которая ссылается на созданный нами узел li, пишем appendChild и в параметре набираем константу taskText которая ссылается на узел span. Готово. При выполнении этого когда узел span станет дочерним к узлу li.
Далее таким же образом создадим checkbox
Добавим этому тегу атрибут type со значением checkbox. Это можно сделать с помощью метода setAttribute. В первом параметре в виде строки мы пишем имя атрибута, во втором его значение. И также делаем его дочерним к узлу li. Данный чекбокс встанет дочерним к тегу ли и будет следовать за тегом спан, так как узел спэн мы сделали дочерним до чекбокса.
Далее создадим кнопки Удалить и Изменить.
Сейчас все эти теги хранятся в оперативной памяти. Перенести их в наш документ можно просто сделав тег ли дочерним к нашему списку.
Далле добавим к чекбоксу слушатель. Сперва пишем переменную которая ссылается на чекбокс, и прикрепляем к ней слушатель, который будет отслеживать событие change. Данное событие отслеживает изменения в таких элементах формы, как
input, select и texarea.
Далее функцию. Нам надо проверить условие – если на чекбоксе стоит галочка, то сделать то-то и то-то. Если нет, то необходимо предпринять другие действия. Проверку можно сделать так. Пишем if, а в скобках пишем ключевое слово this, как помните в обычной функции как здесь, ключевое слово this будет ссылаться на объект, который вызвал метод addEventLisrtener, т.е. на чекбокс. Далее у чекбокса есть свойство чекед, который вернет true, если галочка стоит, и false если не стоит.
Итак, если галочка стоит, то давайте зачеркнем задания с помощью CSS. Т.е. сперва пишем переменную taskText где содержится узел span, далее через точку пишем свойство style, данное свойство вернет объект CSSStyleDeclaration, который предоставит доступ к CSS свойствам узла span. И дальше через точку мы можем изменить какое либо из доступных свойств, которое доступно узлу span. Например textDecoration. В css мы данное свойство писали через тире, но тире нельзя использовать в свойствах, поэтому в JavaScript мы должны использовать стиль lowerCamelCase.
Далее мы должны добавить класс Completed к тегу ли у которого на чекбоксе стоит галочка. Делаем это уже через такую цепочку свойств и методов. Сперва пишем константу которая отвечает за узел li, далее пишем свойство classList который возвращает объект DOMTokenList, который в свою очередь предоставляет нам доступ к управлению классами узла ли, через методы объекта DOMTokenList. В частности нам нужен метод add который добавит класс узлк li. Пишем. В скобках пишем имя классам. Это один из самых популярных способов как добавить класс, но есть также метод className, или также можно добавить класс с помощью метода setAtribute. С помощью setAtribute, добавить вообще любой атрибут, включая класс, с помощью className добавляет, если у тега нет класса, или изменяет существующий класс, так что с ним осторожней. Метод setArtibute также перезапишет существующий класс, если он есть. А наш способ, classList и метод add только добавляет один или несколько классов, но не изменяет существующие.
После всех этих изменений нам надо будет сохранить все изменения в памяти браузера. Для этого мы позже создадим функцию safeTasksToLocalStorage, а пока просто ее вызовем.
Теперь напишем действия если галочка не стоит. Уберем зачеркивание текста, затем удалим класс completed, и также все изменения сохраним в память браузера.
Теперь добавим слушатель к кнопке удалить. Отслеживать мы будем клик, и при клике на кнопку удалить, мы просто удалим тег ли где эта кнопка находится с помощью метода removeChild и в параметре указываем константу с текущим узлом li.
И сохраняем результат в память браузера.
Теперь добавим слушатель к кнопке изменить. Здесь будет много кода. Мы также будем отслеживать клик.
Далее в теле функции сперва проверим содержит ли тег li класс editing. Делается это уже с помощью знакомого нам свойства classList, только теперь мы будем использовать метод контейнс, который вернет true если текст который мы напишем в параметре совпадет с именем класса, и false если не совпадет.
Далее если у такого класса нет, то применим следующие действия:
создадим новое поле input где мы будем редактировать текст. Тип у инпута будет текст, значение текста мы возьмем из ntuf span. Далее мы сделаем этот инпут первым дочерним тегом для тега ли. Как вы могли заметить, я то говорю узлы, то теги, что равнозначно, но конечно правильней говорить узлы, если мы работаем с DOM.
Итак, чтобы сделать тег input первым дочерним узлом для узла li, мы сперва пишем константу которая содержит узел li, затем пишем метод insertBefore, в первом параметре пишем узел который хотим вставить, во втором параметре, пишем узел перед которым вы хотим вставить, это узел span.
А затем удалим узел span. Далее добавим тегу li класс editing, и также добавим id инпуту. Id можно добавить через уже знакомый нам метод setAttribute, или через свойство id. Воспользуемся вторым способом. Для этого id мы уже прописали стиль CSS, вот почему мы его добавляем. Осталось изменить текст кнопки на сохранить. Т.е. теперь когда мы нажмем на кнопку изменить, то появится на этом месте поле для редактирования, с текстом который мы хотим отредактировать, и кнопка изменить поменяет текст на Сохранить.
Далее напишем код, когда мы нажимаем на кнопку Сохранить, т.е. когда у тега li имеется класс editing.
Снова создадим тег span. Сделаем дочерним узлом узла li, присвоим константе input, где мы редактировали текст. Добавим текст тегу спэн из инпута. Далее удалим инпут. Затем удалим класс editing через свойство classList и метода remove. Изменим текст кнопки на изменить, и сохраним данные в память браузера.
Далее после всех этих слушателей снова запустим метод для сохранения информации в браузере. Дело в том, что до этого мы лишь сохраняли в браузер изменения, которые будут когда пользователь поставит галочку, удалит задание, или изменит его, а этот вызов функции сохранит именно добавление новой задачи.
Теперь нам надо написать функцию, которая сохраняет информацию.
В начале создадим пустой массив который назовем tasks. Далее с помощью объекта document выберем все задачи, данный объект вернет нам массив (точнее псевдомассив, так как метод возвращает объект HTMLCollection, что является псевдомассивом, но сейчас это не важно, я потом объясню что такое псевдомассивы), сейчас важно что к этому псевдомассив мы можем применть метод forEach, как и к обычным массивам.
Внутри у нас будет стрелочная функция, в параметр назовем task. В параметр task будет передаваться каждый узел li. В теле функции напишем такой код. Создадим константу taskText, он будет хранить текст задачи. Присвоить ему текст можно таким способом: в значении пишем task, в котором храниться текущий узел li. И в текущем узле мы можем поискать тег span с помощью метода querySelector. Т.е. мы можем применять этот метод не только к объекту document, то также к любому узлу, который содержит дочерние узлы. Ищем мы тег span. Так как этот метод вернет нам первый (а в нашем случае и единственный тег span), мы можем благодаря цепочки свойств и методов получить доступ к его тексту и присвоить его константе.
Далее создадим константу, которая будет указывать есть ли у текущего тега ли класс completed, т.е. стоит ли галочка на чекбоксе. Проверить это можно с помощью свойства classList и contains, который вернет true если класс такой имеется. и false если такого класса нет.
Затем добавим этот текст и это булевское значение в массив в виде объекта. Добавлять в массив мы будем с помощью метода push. В скобках набираем объект. Первый ключ будет называться text, а в качестве значения напишем константу taskText, т.е. текст который находится внутри тега спэн. Второй ключ будет называться completed, в качестве значения будет константа isCompleted, которая будет содержать либо true, либо false. Теперь осталось все это сохранить на компьютер.
Это мы можем сделать с помощью объекта localStorage, который представляет собой веб-хранилище, который хранит текстовую информацию в браузере в виде ключ-значение. Сохранить что либо мы можем с помощью метода setItem, где в первом параметре мы указываем ключ, во воторм значение. Например, name, Vladimir. В нашем случае в качестве ключа мы используем слово tasks, а в качестве значения нам надо сохранить весь массив но в виде текста. И здесь на помощь к нам приходит метод stringlify объекта JSON, который просто переведет наш массив tasks в текст в формате JSON.
Т.е. получается в браузере под ключом tasks будет хранится текст формата JSON.
Функция готова.
Теперь нам осталось написать функцию, которая, при перезагрузке страницы, выведет все задачи хранящиеся в браузере на экран. Функция будет находится внутри слушателя, который будет выполнять ее, когда страница загрузится в браузер. Пишем объект document. AddEventListener. Событие нам нужно DOMContentLoaded, которые выполнится, когда все узлы DOM будут готовы к взаимодействию.
Когда все узлы DOM будут готовы к взаимодействию, то первым делом возьмем данные из браузера с помощью метода LocalSrorage.getItem а затем этот текст в формате JSON преобразуем в массив с помощью метода JSON.Parse и присвоим этот массив константе которую назовем savedTask. Но здесь может быть проблема, вдруг транслятор не найдет значение по ключу tasks. Может вы почистили кеш, и данные удалились. Тогда метод setItem вернет null. Почему именно null, а не undefined, ведь я говорил, что значение null в основном указывает пользователь, а транслятор, если что-то не нашел, то возвращает undefined. Дело в том, что это просто общепринятый подход при работе в работе с хранилищами данных в веб-разработке.
Смотрите, с данной константой мы планируем работать как с массивом, и вызывать метода массива, но если мы получим в значении null, то дальнейший код вызовет ошибку в консоле.
Чтобы этого избежать, мы можем задать значение по умолчанию, делается это с помощью логического оператора ИЛИ. Пишем логических оператор ИЛИ и пустой массив. Здесь логически оператор ИЛИ будет работать так, если значение слева от оператора ИЛИ будет равно: null, undefined, пустой строке, NaN, 0 или false, то константе присвоится значение справа от оператора. И тогда дальнейший код не вызовит ошибку.
Далее воззовем для этого массива метод forEach. В параметре воспользуемся стрелочной функцией. Как помните у нас массив состоит из объектов у которых два ключа: text и isComplited. Т.е. каждый элемент массива это объект. Назовем их task. И внутри функции первым делом вызовем функцию addTask, которая добавит текст из объекта task. Вот так просто добавить задачи из памяти.
Но нам еще надо поставить галочки, где они стояли до перезагрузки браузера, и зачеркнуть в этих местах текст задачи. Это сделать будет уже чуть сложнее. Итак, с помощью AddTask мы добавим текст задачи в список. Теперь нам надо проверить, чему равен ключ isComplted. Если он равен true, т.е. на этой задаче стоит галочка, то мы предпримем такие-то действия. Набираем.
Наша задача, если у объекта isComplited равен true, то необходимо взять тег li на сайте, где текст задачи будет равен текущему тексту объекта, далее у этого тега li взять текст и чекпоинт, и соответственно зачеркнуть текст и поставить галочку на чекпоинт. Надеюсь не сложно объяснил. Давайте сделаем все по порядку.
В начале соберем все теги ли в массив. Назову его allLi. Далее применю к этому массиву метод forEach, чтобы применить действие, только если текст тега ли соответствует тексту объекта. Получается что этот код сработает только в случае если isComplited равен тру. А если он равент тру, то нам надо найти тег ли с текстом как у текущего объекта. Для этого я переберу все теги ли и выберу из них тот, который идет с таким с необходимым нам текстом.
параметр для стрелочной функции я назову, допустим element. Параметр элемент , это по сути тег li. Вытащим у этого элемента таг спэн и чекбокс.
Далее если текст у span такой же как у текста объекта, то зачеркнем текст, добавим галочку к чекбоксу, а тегу ли присвоим класс completed.
Пришло время протестировать наше приложение. Добавлю несколько задач. Отмечу, что например эти задачи выполнены. Перезагружу браузер.
Только проблема если я снова перезагрузку, то не сохраниться, что я выполнил последнее задание. Если снова перезагружу, то уже ничего не поменяется. Исправим мы данный баг на следующем уроке, где изучим тему Отладки программы. А пока на этом всё, до встречи на следующем уроке.