JQUERY one page application mvp

Недавно один мой знакомый вайтишник спросил как запилить редактирование данных в бд, ну Вы знаете, так чтобы и страница на каждый чих не перезагружалась и чтобы формочка редактирования объекта появлялась итд. Ну вот этим мы сейчас и займёмся.

Подробнее о задаче

У нас есть какая-то отдельная апи которая возвращает список чего-то, пусть это будут, например, котики: GET: /api/cats.json
[
  {
    "id": 1,
    "name": "Барсик",
    "breed": "Persian",
    "age": 12,
    "sex": "male"
  },
...
]
и по каждому котику будет ещё дополнительная апи, с дополнительной информацией: GET /api/cats/1.json
{
  "id": 1,
  "name": "Барсик",
  "breed": "Persian",
  "age": 12,
  "sex": "male",
  "owner": {
    "phone": "+7888999000",
    "name": "Сычёв",
    "city": "Мухосранск"
  }
}

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

Собираем каркас приложения

Итак, нам нужно подключить, стили, jquery, main.js в который положим весь наш код и зафигачить минимальную index.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ru" lang="ru">
<head>
  <title>JQUERY interactive example</title>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <meta name="viewport" content="initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" crossorigin="anonymous">
  <link href="./css/main.css" type="text/css" rel="stylesheet">
</head>
<body>

<div class="container">
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
    <a class="navbar-brand" href="#">JQUERY example</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
      <div class="navbar-nav">
        <!-- какие-то элементы меню -->
      </div>
    </div>
  </nav>
  <br/>
</div>

<div class="container">
... content
</div>

<script src="https://code.jquery.com/jquery-3.4.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script src="./js/main.js"></script>
</body>
</html>

Подключаем таблицу

Для начала фигачим шаблон:
  <table class="table table-hover">
    <thead>
      <tr>
        <th>id</th>
        <th>Имя</th>
        <th>Порода</th>
        <th>Возраст</th>
        <th>Пол</th>
        <th></th>
      </tr>
    </thead>
    <tbody id="cats_table_body"></tbody>
  </table>

id="cats_table_body" нам нужен для лёгкого поиска тела таблицы по ID. Ну и да, делаем нашу первую js функцию, которая будет загружать список котиков с сервера:

function loadCatsList(url) {
  $.get(url)
  .then( data=> {
    console.log('* data', data);
  })
} 

Функция получает url по которому лежат котики, и если сервер отдаёт правильные заголовки, то jquery автоматически распарсит json в объект.

Теперь нам надо написать функцию, которая сгенерирует тело таблицы, по полученным данным:

function genTableBody(data) {
  let result = '';
  
  for(let item of data) {
    result += '<tr>';
    
    for(let index in item)
      result += `<td>${item[ index ]}</td>`;
    
    result += `<td><a class="btn btn-primary" href="">Owner info</a></td>`;
    result += '</tr>';
  }
  
  return result;
}

Теперь мы можем вставить сгенерированные строки в таблицу:

function loadCatsList(url) {
  $.get(url)
  .then( data=> {
    $('#cats_table_body').html(genTableBody(data));
  });
} 

Сейчас если мы кликнем по кнопке "Owner info", то ничего не произойдёт, нужно это исправить. Для этого, вешаем на кнопку вызов функции- обработчика события:

result += `<td><a class="btn btn-primary" href="javascript:catClick(${item.id})">Owner info</a></td>`;

И пишем тестовую функцию- обработчик:

function catClick(id) {
  alert('on catClick ' + id);
}

Если сейчас всё у нас без ошибок, то по клику на кнопку должен появляться алерт:

Создаём форму с дополнительной информацией

В первую очередь нужно написать стандартную bootstrap формочку, которую нужно положить, опять ж, в стандартное bootstrap всплывающее окошко:

<div class="modal fade" id="ownerform" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">Cat owner</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <div class="row">
          <div class="col-sm-12">
          
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Phone:</label>
              <div class="col-sm-10">
                <input type="text" readonly class="form-control-plaintext" id="ownerform_phone" value="">
              </div>
            </div>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Name:</label>
              <div class="col-sm-10">
                <input type="text" readonly class="form-control-plaintext" id="ownerform_name" value="">
              </div>
            </div>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">City:</label>
              <div class="col-sm-10">
                <input type="text" readonly class="form-control-plaintext" id="ownerform_city" value="">
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>

Да, чтобы всё работало красиво, приходится копипастить могабукаф. Но благо, для этого есть типовые примеры на сайте с документацией.

Ок, теперь у нас есть форма, есть кнопка с обработчиком события, давайте навесим на неё активацию bootstrap modal:

function catClick(id) {
  //alert('on catClick ' + id);
  $("#ownerform").modal()
}

И cделаем api запрос за расширенной информацией по котику:

function catClick(id) {
  $.get(`./api/cats/${id}.json`)
  .then( data => {
    console.log('* DATA:', data);
    $("#ownerform").modal()
  });
}

Теперь мы сперва получаем данные с помощью $.get(`./api/cats/${id}.json`), и уже только после этого, показываем формочку. Остался последний этап, закинуть данные полученные по апи, в саму форму:

function catClick(id) {
  $.get(`./api/cats/${id}.json`)
  .then( data => {
    $('#ownerform_phone').val(data.owner.phone);
    $('#ownerform_name').val(data.owner.name);
    $('#ownerform_city').val(data.owner.city);
    $("#ownerform").modal();
  });
}

На этом этапе, если у нас всё ок, то увидим следующее:

Форма редактирования/добавления

Ну и для закрепления, давайте создадим форму редактирования и добавления данных. Это будет ода форма, сразу на 2 действия, чтобы не создавать 2 отдельные формы. Как обычно, копипастим шаблон модального окошка с getbootstrap и в нём фигачим форму:

<div class="modal fade" id="editform" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLongTitle">Cat</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <div class="row">
          <div class="col-sm-6">
            
            <h3>Cat</h3>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Name:</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="editform_name" value="">
              </div>
            </div>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Breed:</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="editform_breed" value="">
              </div>
            </div>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Age:</label>
              <div class="col-sm-10">
                <input type="number" class="form-control" id="editform_age" value="">
              </div>
            </div>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Sex:</label>
              <div class="col-sm-10">
                <select class="form-control" id="editform_sex">
                  <option value="male">male</option>
                  <option value="female">female</option>
                  <option value="no">not sure</option>
                </select>
              </div>
            </div>
          
          </div>
          <div class="col-sm-6">
            
            <h3>Owner</h3>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Phone:</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="editform_phone" value="">
              </div>
            </div>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">Name:</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="editform_owner_name" value="">
              </div>
            </div>
            
            <div class="form-group row">
              <label class="col-sm-2 col-form-label">City:</label>
              <div class="col-sm-10">
                <input type="text" class="form-control" id="editform_city" value="">
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary" data-dismiss="modal">Save</button>
      </div>
    </div>
  </div>
</div>

Подправляем табличку, в неё нужно добавить кнопку активации редактирования:

result += `<td><a class="btn btn-primary" href="javascript:catClick(${item.id})">Owner info</a> `;
result += `<a class="btn btn-primary" href="javascript:catEdit(${item.id})">Edit</a></td>`;

И фигачим функцию catEdit, которая далет почти то же самое что и catClick, только будет активировать другую форму, и данные будет запихивать в другие эдиты:

function catEdit(id) {
  $.get(`./api/cats/${id}.json`)
  .then( data => {
    addEditAction = 'edit';
    $('#editform_phone').val(data.owner.phone);
    $('#editform_owner_name').val(data.owner.name);
    $('#editform_city').val(data.owner.city);
    $('#editform_name').val(data.name);
    $('#editform_breed').val(data.breed);
    $('#editform_age').val(data.age);
    $('#editform_sex').val(data.sex);
    $("#editform").modal();
  });
}

И ещё нужно добавить глобальную переменную, в которой будет лежать инструкция, что нам делать с данными в форме:

var addEditAction = 'none';

Сейчас, если кликнуть по кнопке "edit", то будет следующая картина:

Осталось повесить обработчик нажатия кнопки "save":

function saveEditClick() {
  let formData = {
    owner: {}
  };
  
  formData.owner.phone = $('#editform_phone').val();
  formData.owner.name = $('#editform_owner_name').val();
  formData.owner.city = $('#editform_city').val();
  formData.name = $('#editform_name').val();
  formData.breed = $('#editform_breed').val();
  formData.age = $('#editform_age').val();
  formData.sex = $('#editform_sex').val();
  
  if( addEditAction == 'edit' )
    $.ajax({
      type: "PUT",
      url: './api/cats',
      data: formData
    });
  
  if( addEditAction == 'add' )
    $.ajax({
      type: "POST",
      url: './api/cats',
      data: formData
    });
  
  addEditAction = 'none';
  $('#editform').modal('hide');
}

А ещё нам не хватает кнопки "создать котика". Давайте зафигачим её в пустой хедер таблички:

<button class="btn btn-primary" onClick="addCat();">Add cat</button>

И функция addCat:

function addCat() {
  addEditAction = 'add';
  $('#editform_phone').val('');
  $('#editform_owner_name').val('');
  $('#editform_city').val('');
  $('#editform_name').val('');
  $('#editform_breed').val('');
  $('#editform_age').val(0);
  $('#editform_sex').val('no');
  $("#editform").modal();
}

Готовый пример можно посмотреть тут: https://allwebstuff.info/examples/jquery_interactive_example/, архив в исходниками на гите. Единственно, так как я ленивая жопа, я не сделал на сервере обработчики пост и пут запросов, по этому, они будут отваливаться. Но посмотреть, работают они или нет, можно в дев консоли браузера:

Плюсы и минусы построения one-page apps на jquery

Если вся наша приложунька- это действительно только одна табличка и пара не сложных формочек, то минусов тут и нет. Весь код понятен, лишних зависимостей нет. Но, если приложунька выростает до хотя бы нескольких страниц, со сложной логикой, кучей элементов, то мы сталкиваемся с проблемами:

  • Первая проблема- это всё трудно поддерживать, т.к весь html свален в 1 файл, весь js в одном файле, нет разделения на модули, итд.
  • Ещё, если мы зафигачим большую хтмльку, то браузер банально начнёт тормозить.
  • И все функции и всякие переменный у нас лежат в общем неймспейсе, что 100% потащит кучу багов.

Вот чтобы решить все эти проблемы, ну и дать, ещё немного плюшек сверху, напридумывали всякие vue/react/angular итд. Но про них как-нибудь попозже.



Антироссийские протесты в Грузии

Второй деть в Грузии идут антироссийские протесты. Если Вы ещё не в курсе, меня ситуация в Грузии касается самым непосредственным образом. У меня там ип и счета в банке. Но я не паникую и вот почему.

Впечатление от windows 10 после >10 лет жизни на linux

Я очень, очень давно живу только на линупсе. Последняя винда, которая стояла у меня на компе- это первые версии windows 7. И тут меня приспичило погамать в старкрафт 2, который под вайном ваще никак не хотел стартовать. ИИИиииии, внезапно винда стала почти нормальной! Уже догнала по возможностям кеды 15 летней давности))


(0) Комментариев