Делаем свой планировщик задач

Трел­ло — удоб­ная шту­ка для веде­ния про­ек­тов. Там все зада­чи пред­став­ле­ны в виде коло­нок, а каж­дая колон­ка отве­ча­ет за что-то своё. Давай­те сде­ла­ем нечто подоб­ное, тем более что всё для это­го у нас уже есть:

  • Bootstrap, с кото­рым мы позна­ко­ми­лись, пока созда­ва­ли стра­ни­цы с котиками;
  • спи­сок задач, где мож­но добав­лять новые и уда­лять ненуж­ные пункты.

Если это объ­еди­нить, полу­чит­ся что-то похо­жее на Trello. Логи­ка такая:

  • у нас будет несколь­ко коло­нок на стра­ни­це (за это отве­ча­ет Bootstrap);
  • каж­дая колон­ка будет отве­чать за свои зада­чи и назы­вать­ся по-своему;
  • в каж­дой колон­ке мож­но добав­лять и уда­лять зада­чи неза­ви­си­мо от осталь­ных (а за это отве­ча­ет скрипт из про­шло­го спис­ка задач).

Мы сде­ла­ем 4 колон­ки, но вы для себя може­те сде­лать их столь­ко, сколь­ко нуж­но. Един­ствен­ное огра­ни­че­ние — Bootstrap не поз­во­ля­ет делать боль­ше 12 коло­нок. Наде­ем­ся, вам это­го хватит.

Готовим колонки и настраиваем стили

За осно­ву возь­мём стра­ни­цу из ста­тьи про коти­ков и сде­ла­ем с ней следующее:

  • пере­не­сём в неё сти­ли из стра­ни­цы со спис­ком задач;
  • доба­вим недо­ста­ю­щие сти­ли для заго­лов­ков коло­нок и всей страницы;
  • под­го­то­вим пустые 4 колон­ки и настро­им их раз­мер под раз­ную шири­ну экрана.

Если мы всё сде­ла­ем пра­виль­но, то полу­чит­ся следующее:

<!DOCTYPE html>
<html>
<!-- служебная часть -->

<head>
  <!-- заголовок страницы -->
  <title>Управление проектами</title>
  <!-- настраиваем служебную информацию для браузеров -->
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <!-- задаём CSS-стили прямо здесь же, чтобы всё было в одном файле -->
  <style type="text/css">
    /*Задаём общие параметры для всей страницы: шрифт и отступы*/
    body {
      text-align: center;
      margin: 10;
      font-family: Verdana, Arial, sans-serif;
      font-size: 16px;
    }

    /* Внешний вид заголовка первого уровня*/
    h1 {
      margin-bottom: 50px;
      font-weight: bold;
    }

    /* Внешний вид заголовка второго уровня*/
    h2 {
      font-size: 22px;
    }

    /* Настраиваем внешний вид поля ввода*/
    input {
      display: inline-block;
      margin: 20px auto;
      border: 2px solid #eee;
      padding: 10px 20px;
      font-family: Verdana, Arial, sans-serif;
      font-size: 16px;
    }

    /*Как будет выглядеть каждый элемент нашего списка*/
    .tdItem {
      text-align: left;
      padding: 10px;
      cursor: default;
      border-radius: 7px;
    }

    /*Что произойдёт, когда мы наведём курсор на любую задачу из списка*/
    .tdItem:hover {
      background-color: lightblue;
    }

    /*Закончили со стилями*/
  </style>
  <!-- закрываем служебную часть страницы -->
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-12">
        <h1>Управление проектами, делами и собой</h1>
      </div>
    </div>
  </div>
  <div class="container">
    <div class="row">
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <!-- содержимое первой колонки -->
      </div>
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <!-- содержимое второй колонки -->
      </div>
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <!-- содержимое третьей колонки -->
      </div>
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <!-- содержимое четвёртой колонки -->
      </div>
    </div>
  </div>
</body>
<!-- конец всей страницы -->

</html>

Наполняем колонки

У каж­дой колон­ки долж­но быть своё назва­ние, поэто­му назо­вём их «Сде­лать», «Позво­нить», «Напи­сать» и «Идеи».

Мы пом­ним, что в каж­дой колон­ке дол­жен быть отдель­ный спи­сок задач, поэто­му возь­мём кусок кода из про­шло­го мате­ри­а­ла. Глав­ное, что нам нуж­но учесть, — что у каж­до­го спис­ка долж­но быть своё уни­каль­ное имя, что­бы мы их не пере­пу­та­ли в про­цес­се. Для это­го мы про­сто доба­вим циф­ры от одно­го до четы­рёх к назва­нию каж­до­го спис­ка. Напри­мер, было «tldDiv», а ста­ло «tld1Div, «tld2Div» и так далее.

В ито­ге колон­ки будут выгля­деть так:

<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
  <h2 class="todo__caption">Позвонить</h2>
  <!-- Поле ввода, куда пишем новые задачи «Позвонить» -->
  <div id="tdl2App">
    <input type="text" class="form-control" placeholder="Новая задача">
    <!-- Создаём пока ещё пустой список «Позвонить» -->
    <div class="tdl2Div">
      <ul class="List list-unstyled">
        <!-- Тут появятся наши задачи, когда мы их добавим -->
      </ul>
    </div>
  </div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
  <h2 class="todo__caption">Написать</h2>
  <!-- Поле ввода, куда пишем новые задачи «Написать»-->
  <div id="tdl3App">
    <input type="text" class="form-control" placeholder="Новая задача">
    <!-- Создаём пока ещё пустой список «Написать» -->
    <div class="tdl3Div">
      <ul class="List list-unstyled">
        <!-- Тут появятся наши задачи, когда мы их добавим -->
      </ul>
    </div>
  </div>
</div>
<div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
  <h2 class="todo__caption">Идеи</h2>
  <!-- Поле ввода, куда пишем новые задачи «Идеи» -->
  <div id="tdl4App">
    <input type="text" class="form-control" placeholder="Новая задача">
    <!-- Создаём пока ещё пустой список «Идеи» -->
    <div class="tdl4Div">
      <ul class="List list-unstyled">
        <!-- Тут появятся наши задачи, когда мы их добавим -->
      </ul>
    </div>
  </div>
</div>
Выгля­дит инте­рес­но, но пока ниче­го не работает. 

Переносим и расширяем скрипт

Теперь наша зада­ча — взять про­шлый скрипт и адап­ти­ро­вать его под наши цели. Нам нуж­но, что­бы каж­дая колон­ка с зада­ча­ми рабо­та­ла авто­ном­но и не зави­се­ла от дру­гих, поэто­му сей­час скрипт уве­ли­чит­ся в 4 раза — для каж­до­го спис­ка будет свой код.

Сра­зу ска­жем: это пло­хое реше­ние. Копи­паст кода для рас­ши­ре­ния функ­ци­о­наль­но­сти — это путь к дол­гой мучи­тель­ной смер­ти под зава­ла­ми спагетти-кода. По-хорошему нуж­но заво­ра­чи­вать это при­ло­же­ние в клас­сы, делать объ­ек­ты и рабо­тать с ними.

Мы всё это понимаем.

Но копи­па­с­та быстрее. 

Если собрать все изме­не­ния, кото­рые нам пона­до­бят­ся, то полу­чит­ся такой список:

  • В самом нача­ле, когда мы заво­дим пере­мен­ные под наши зада­чи, у нас теперь не про­сто List и Mask, а List1, Mask1, List2, Mask2 и так далее. Это нуж­но для того, что­бы не сме­ши­вать спис­ки задач.
  • Поме­ня­лось и содер­жи­мое этих пере­мен­ных — теперь там есть циф­ры, кото­рые пока­зы­ва­ют, к како­му спис­ку они отно­сят­ся. Важ­ный момент — у нас дли­на пре­фик­са «tdl_» уве­ли­чи­лась на один сим­вол и ста­ла, напри­мер, «tdl1_». Это нам нуж­но будет учесть в сере­дине скрипта.
  • Так как спис­ки долж­ны быть авто­ном­ны, то и индек­сы, кото­рые мы добав­ля­ем к каж­до­му эле­мен­ту, тоже долж­ны не зави­сеть друг от дру­га. Отсю­да и пере­мен­ные вида element_Id_1 и number_Id_1.


Един­ствен­ное, что оста­лось преж­ним, — обра­бот­ка уда­ле­ния зада­чи. В обра­бот­чи­ке всё уже изна­чаль­но орга­ни­зо­ва­но так, что скрипт сра­зу полу­ча­ет все дан­ные, кото­рые ему нуж­ны, неза­ви­си­мо от колон­ки и поряд­ко­во­го номе­ра задачи.

Сам скрипт полу­ча­ет­ся таким:

// Подключаем JQuery
// Пишем скрипт, который будет обрабатывать наши задачи и хранить их на нашем устройстве
// Заводим переменные под наши задачи
var List1 = $('#tdl1App ul');
var Mask1 = 'tdl1_';
var List2 = $('#tdl2App ul');
var Mask2 = 'tdl2_';
var List3 = $('#tdl3App ul');
var Mask3 = 'tdl3_';
var List4 = $('#tdl4App ul');
var Mask4 = 'tdl4_';
// Функция, которая берёт из памяти наши задачи и делает из них список
function showTasks() {
  // Узнаём размер хранилища
  var Storage_size = localStorage.length;
  // Если в хранилище что-то есть…
  if (Storage_size > 0) {
    // то берём и добавляем это в задачи  
    for (var i = 0; i < Storage_size; i++) {
      var key = localStorage.key(i);
      // обрабатываем первый список
      if (key.indexOf(Mask1) == 0) {
        // и делаем это элементами списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', key)
          .text(localStorage.getItem(key))
          .appendTo(List1);
      }
      // обрабатываем второй список
      if (key.indexOf(Mask2) == 0) {
        // и делаем это элементами списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', key)
          .text(localStorage.getItem(key))
          .appendTo(List2);
      }
      // обрабатываем третий список
      if (key.indexOf(Mask3) == 0) {
        // и делаем это элементами списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', key)
          .text(localStorage.getItem(key))
          .appendTo(List3);
      }
      // обрабатываем четвёртый список
      if (key.indexOf(Mask4) == 0) {
        // и делаем это элементами списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', key)
          .text(localStorage.getItem(key))
          .appendTo(List4);
      }
    }
  }
}
// Сразу вызываем эту функцию, вдруг в памяти уже остались задачи с прошлого раза
showTasks();
// Следим, когда пользователь напишет новую задачу в первое поле ввода и нажмёт Enter
$('#tdl1App input').on('keydown', function (e) {
  if (e.keyCode != 13) return;
  var str = e.target.value;
  e.target.value = "";
  // Если в поле ввода было что-то написано — начинаем обрабатывать
  if (str.length > 0) {
    var number_Id_1 = 0;
    List1.children().each(function (index, el) {
      var element_Id_1 = $(el).attr('data-itemid').slice(5);
      if (element_Id_1 > number_Id_1)
        number_Id_1 = element_Id_1;
    })
    number_Id_1++;
    // Отправляем новую задачу сразу в память
    localStorage.setItem(Mask1 + number_Id_1, str);
    // и добавляем её в конец списка
    $('<li></li>').addClass('tdItem')
      .attr('data-itemid', Mask1 + number_Id_1)
      .text(str).appendTo(List1);
  }
});
// Следим, когда пользователь напишет новую задачу во второе поле ввода и нажмёт Enter
$('#tdl2App input').on('keydown', function (e) {
  if (e.keyCode != 13) return;
  var str = e.target.value;
  e.target.value = "";
  // Если в поле ввода было что-то написано — начинаем обрабатывать
  if (str.length > 0) {
    var number_Id_2 = 0;
    List2.children().each(function (index, el) {
      var element_Id_2 = $(el).attr('data-itemid').slice(5);
      if (element_Id_2 > number_Id_2)
        number_Id_2 = element_Id_2;
    })
    number_Id_2++;
    // Отправляем новую задачу сразу в память
    localStorage.setItem(Mask2 + number_Id_2, str);
    // и добавляем её в конец списка
    $('<li></li>').addClass('tdItem')
      .attr('data-itemid', Mask2 + number_Id_2)
      .text(str).appendTo(List2);
  }
});
// Следим, когда пользователь напишет новую задачу в третье поле ввода и нажмёт Enter
$('#tdl3App input').on('keydown', function (e) {
  if (e.keyCode != 13) return;
  var str = e.target.value;
  e.target.value = "";
  // Если в поле ввода было что-то написано — начинаем обрабатывать
  if (str.length > 0) {
    var number_Id_3 = 0;
    List3.children().each(function (index, el) {
      var element_Id_3 = $(el).attr('data-itemid').slice(5);
      if (element_Id_3 > number_Id_3)
        number_Id_3 = element_Id_3;
    })
    number_Id_3++;
    // Отправляем новую задачу сразу в память
    localStorage.setItem(Mask3 + number_Id_3, str);
    // и добавляем её в конец списка
    $('<li></li>').addClass('tdItem')
      .attr('data-itemid', Mask3 + number_Id_3)
      .text(str).appendTo(List3);
  }
});
// Следим, когда пользователь напишет новую задачу в четвёртое поле ввода и нажмёт Enter
$('#tdl4App input').on('keydown', function (e) {
  if (e.keyCode != 13) return;
  var str = e.target.value;
  e.target.value = "";
  // Если в поле ввода было что-то написано — начинаем обрабатывать
  if (str.length > 0) {
    var number_Id_4 = 0;
    List4.children().each(function (index, el) {
      var element_Id_4 = $(el).attr('data-itemid').slice(5);
      if (element_Id_4 > number_Id_4)
        number_Id_4 = element_Id_4;
    })
    number_Id_4++;
    // Отправляем новую задачу сразу в память
    localStorage.setItem(Mask4 + number_Id_4, str);
    // и добавляем её в конец списка
    $('<li></li>').addClass('tdItem')
      .attr('data-itemid', Mask4 + number_Id_4)
      .text(str).appendTo(List4);
  }
});
// По клику на задаче — убираем её из списка
$(document).on('click', '.tdItem', function (e) {
  // Находим задачу, по которой кликнули
  var jet = $(e.target);
  // Убираем её из памяти
  localStorage.removeItem(jet.attr('data-itemid'));
  // и убираем её из списка
  jet.remove();
})
// Закончился основной скрипт

Собираем готовую страницу

Нам оста­лось собрать всё вме­сте и про­ве­рить, как это рабо­та­ет. Если буде­те добав­лять колон­ки — не забудь­те про новые назва­ния для пере­мен­ных и про име­на эле­мен­тов в самих колон­ках. На теку­щем эта­пе мы добав­ля­ем всё вруч­ную, но в буду­щей вер­сии сде­ла­ем гене­ра­тор нуж­но­го чис­ла колонок.

<!DOCTYPE html>
<html>
<!-- служебная часть -->

<head>
  <!-- заголовок страницы -->
  <title>Управление проектами</title>
  <!-- настраиваем служебную информацию для браузеров -->
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
  <!-- задаём CSS-стили прямо здесь же, чтобы всё было в одном файле -->
  <style type="text/css">
    /*Задаём общие параметры для всей страницы: шрифт и отступы*/
    body {
      text-align: center;
      margin: 10;
      font-family: Verdana, Arial, sans-serif;
      font-size: 16px;
    }

    /* Внешний вид заголовка первого уровня*/
    h1 {
      margin-bottom: 50px;
      font-weight: bold;
    }

    /* Внешний вид заголовка второго уровня*/
    h2 {
      font-size: 22px;
    }

    /* Настраиваем внешний вид поля ввода*/
    input {
      display: inline-block;
      margin: 20px auto;
      border: 2px solid #eee;
      padding: 10px 20px;
      font-family: Verdana, Arial, sans-serif;
      font-size: 16px;
    }

    /*Как будет выглядеть каждый элемент нашего списка*/
    .tdItem {
      text-align: left;
      padding: 10px;
      cursor: default;
      border-radius: 7px;
    }

    /*Что произойдёт, когда мы наведём курсор на любую задачу из списка*/
    .tdItem:hover {
      background-color: lightblue;
    }

    /*Закончили со стилями*/
  </style>
  <!-- закрываем служебную часть страницы -->
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-12">
        <h1>Управление проектами, делами и собой</h1>
      </div>
    </div>
  </div>
  <div class="container">
    <div class="row">
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <h2 class="todo__caption">Сделать</h2>
        <!-- Поле ввода, куда пишем новые задачи «Сделать»-->
        <div id="tdl1App">
          <input type="text" class="form-control" placeholder="Новая задача">
          <!-- Создаём пока ещё пустой список «Сделать» -->
          <div class="tdl1Div">
            <ul class="List list-unstyled">
              <!-- Тут появятся наши задачи, когда мы их добавим -->
            </ul>
          </div>
        </div>
      </div>
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <h2 class="todo__caption">Позвонить</h2>
        <!-- Поле ввода, куда пишем новые задачи «Позвонить» -->
        <div id="tdl2App">
          <input type="text" class="form-control" placeholder="Новая задача">
          <!-- Создаём пока ещё пустой список «Позвонить» -->
          <div class="tdl2Div">
            <ul class="List list-unstyled">
              <!-- Тут появятся наши задачи, когда мы их добавим -->
            </ul>
          </div>
        </div>
      </div>
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <h2 class="todo__caption">Написать</h2>
        <!-- Поле ввода, куда пишем новые задачи «Написать»-->
        <div id="tdl3App">
          <input type="text" class="form-control" placeholder="Новая задача">
          <!-- Создаём пока ещё пустой список «Написать» -->
          <div class="tdl3Div">
            <ul class="List list-unstyled">
              <!-- Тут появятся наши задачи, когда мы их добавим -->
            </ul>
          </div>
        </div>
      </div>
      <div class="col-12 col-sm-6 col-md-3 col-lg-3 col-xl-3">
        <h2 class="todo__caption">Идеи</h2>
        <!-- Поле ввода, куда пишем новые задачи «Идеи» -->
        <div id="tdl4App">
          <input type="text" class="form-control" placeholder="Новая задача">
          <!-- Создаём пока ещё пустой список «Идеи» -->
          <div class="tdl4Div">
            <ul class="List list-unstyled">
              <!-- Тут появятся наши задачи, когда мы их добавим -->
            </ul>
          </div>
        </div>
      </div>
    </div>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js">
  </script>
  <!-- Пишем скрипт, который будет обрабатывать наши задачи и хранить их на нашем устройстве -->
  <script>
    // Заводим переменные под наши задачи
    var List1 = $('#tdl1App ul');
    var Mask1 = 'tdl1_';
    var List2 = $('#tdl2App ul');
    var Mask2 = 'tdl2_';
    var List3 = $('#tdl3App ul');
    var Mask3 = 'tdl3_';
    var List4 = $('#tdl4App ul');
    var Mask4 = 'tdl4_';
    // Функция, которая берёт из памяти наши задачи и делает из них список
    function showTasks() {
      // Узнаём размер хранилища
      var Storage_size = localStorage.length;
      // Если в хранилище что-то есть…
      if (Storage_size > 0) {
        // то берём и добавляем это в задачи  
        for (var i = 0; i < Storage_size; i++) {
          var key = localStorage.key(i);
          // обрабатываем первый список
          if (key.indexOf(Mask1) == 0) {
            // и делаем это элементами списка
            $('<li></li>').addClass('tdItem')
              .attr('data-itemid', key)
              .text(localStorage.getItem(key))
              .appendTo(List1);
          }
          // обрабатываем второй список
          if (key.indexOf(Mask2) == 0) {
            // и делаем это элементами списка
            $('<li></li>').addClass('tdItem')
              .attr('data-itemid', key)
              .text(localStorage.getItem(key))
              .appendTo(List2);
          }
          // обрабатываем третий список
          if (key.indexOf(Mask3) == 0) {
            // и делаем это элементами списка
            $('<li></li>').addClass('tdItem')
              .attr('data-itemid', key)
              .text(localStorage.getItem(key))
              .appendTo(List3);
          }
          // обрабатываем четвёртый список
          if (key.indexOf(Mask4) == 0) {
            // и делаем это элементами списка
            $('<li></li>').addClass('tdItem')
              .attr('data-itemid', key)
              .text(localStorage.getItem(key))
              .appendTo(List4);
          }
        }
      }
    }
    // Сразу вызываем эту функцию, вдруг в памяти уже остались задачи с прошлого раза
    showTasks();
    // Следим, когда пользователь напишет новую задачу в первое поле ввода и нажмёт Enter
    $('#tdl1App input').on('keydown', function (e) {
      if (e.keyCode != 13) return;
      var str = e.target.value;
      e.target.value = "";
      // Если в поле ввода было что-то написано — начинаем обрабатывать
      if (str.length > 0) {
        var number_Id_1 = 0;
        List1.children().each(function (index, el) {
          var element_Id_1 = $(el).attr('data-itemid').slice(5);
          if (element_Id_1 > number_Id_1)
            number_Id_1 = element_Id_1;
        })
        number_Id_1++;
        // Отправляем новую задачу сразу в память
        localStorage.setItem(Mask1 + number_Id_1, str);
        // и добавляем её в конец списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', Mask1 + number_Id_1)
          .text(str).appendTo(List1);
      }
    });
    // Следим, когда пользователь напишет новую задачу во второе поле ввода и нажмёт Enter
    $('#tdl2App input').on('keydown', function (e) {
      if (e.keyCode != 13) return;
      var str = e.target.value;
      e.target.value = "";
      // Если в поле ввода было что-то написано — начинаем обрабатывать
      if (str.length > 0) {
        var number_Id_2 = 0;
        List2.children().each(function (index, el) {
          var element_Id_2 = $(el).attr('data-itemid').slice(5);
          if (element_Id_2 > number_Id_2)
            number_Id_2 = element_Id_2;
        })
        number_Id_2++;
        // Отправляем новую задачу сразу в память
        localStorage.setItem(Mask2 + number_Id_2, str);
        // и добавляем её в конец списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', Mask2 + number_Id_2)
          .text(str).appendTo(List2);
      }
    });
    // Следим, когда пользователь напишет новую задачу в третье поле ввода и нажмёт Enter
    $('#tdl3App input').on('keydown', function (e) {
      if (e.keyCode != 13) return;
      var str = e.target.value;
      e.target.value = "";
      // Если в поле ввода было что-то написано — начинаем обрабатывать
      if (str.length > 0) {
        var number_Id_3 = 0;
        List3.children().each(function (index, el) {
          var element_Id_3 = $(el).attr('data-itemid').slice(5);
          if (element_Id_3 > number_Id_3)
            number_Id_3 = element_Id_3;
        })
        number_Id_3++;
        // Отправляем новую задачу сразу в память
        localStorage.setItem(Mask3 + number_Id_3, str);
        // и добавляем её в конец списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', Mask3 + number_Id_3)
          .text(str).appendTo(List3);
      }
    });
    // Следим, когда пользователь напишет новую задачу в четвёртое поле ввода и нажмёт Enter
    $('#tdl4App input').on('keydown', function (e) {
      if (e.keyCode != 13) return;
      var str = e.target.value;
      e.target.value = "";
      // Если в поле ввода было что-то написано — начинаем обрабатывать
      if (str.length > 0) {
        var number_Id_4 = 0;
        List4.children().each(function (index, el) {
          var element_Id_4 = $(el).attr('data-itemid').slice(5);
          if (element_Id_4 > number_Id_4)
            number_Id_4 = element_Id_4;
        })
        number_Id_4++;
        // Отправляем новую задачу сразу в память
        localStorage.setItem(Mask4 + number_Id_4, str);
        // и добавляем её в конец списка
        $('<li></li>').addClass('tdItem')
          .attr('data-itemid', Mask4 + number_Id_4)
          .text(str).appendTo(List4);
      }
    });
    // По клику на задаче — убираем её из списка
    $(document).on('click', '.tdItem', function (e) {
      // Находим задачу, по которой кликнули
      var jet = $(e.target);
      // Убираем её из памяти
      localStorage.removeItem(jet.attr('data-itemid'));
      // и убираем её из списка
      jet.remove();
    })
  // Закончился основной скрипт
  </script>
</body>
<!-- конец всей страницы -->

</html>

Что дальше

Что­бы этот про­дукт стал бли­же к иде­а­лу, мож­но сде­лать так:

  • с помо­щью клас­сов и объ­ек­тов упо­ря­до­чить созда­ние новых коло­нок — не копи­па­стить код, а делать всё через объекты;
  • доба­вить воз­мож­ность пере­тас­ки­вать зада­чи меж­ду колонками;
  • сде­лать ком­мен­та­рии к каж­дой задаче;
  • раз­ре­шить изме­не­ние заго­лов­ков у коло­нок пря­мо на стра­ни­це (contentEditable нам в помощь);
  • настро­ить фон.

Ско­ро добе­рём­ся и до этого!