Full ajax или cкажи нет перезагрузкам, ключевые моменты

в 11:10, , рубрики: ajax, html, jquery, аниме, каталог, сайт, сериалы, фильмы

Full ajax или cкажи нет перезагрузкам, ключевые моменты

О чем это я?

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

Так и была рождена идея нового автоматического сервиса по отмечанию сериалов, аниме и фильмов. Да, сейчас конечно кто-то скажет, что таких сервисов уже туча, что есть и автоматические отмечалки, установи только их плеер, смотри в оригинале чтобы определило, по определенному назови файл и т.д… Скажу сразу, все сервисы были перепробованы или можете считать что просто хотелка захотела ;) Вместе с этим пришло понимание, что это должна быть не только отмечалка серий, а еще полноценный каталог аниме, тв, фильмов с трейлерами, листом того что просмотрел, скриншотами серий, что смотреть дальше, рейтингами и так далее, так что без перфекционизма не обошлось. И одним из ключевых решений было делать сервис полностью с использованием ajax’a. О чём и пойдет речь далее.

Цель

— Загрузка каждого элемента должна происходить блоками не вызывая перезагрузку страницы. То-есть грузить только то что нужно
— Переход назад также недолжен делать перезагрузку страницы
— Загрузка полного урла должна собирать все блоки в единый html без ajax для поисковиков
— Любой ajax запрос меняет url (HTML5)
Вроде бы прописные истинны, да и что его там делать. Да вот не все так просто. Попробуем разобраться.

Структура сайта

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

Как работает среднестатистический сайт.

  • пользователь посещает страницу
  • достаются все необходимые данные, делается авторизация пользователя, распихивается все по нужным переменным
  • делается echo всей этой кухни что мы наварили

В ajax же сайте, каждый блок что планируется подгружаться в дальнейшем должен быть полностью самостоятелен.
Получается:

  • пользователь заходит на страницу
  • делается авторизация и распихивается по нужным переменным
  • делается echo хидера где эти переменные используются
  • достаются необходимые данные для следующего блока (скажем список категорий)
  • выводится список категорий

И т.д… То-есть те же запросы к базам данных могут встречаться в нижней части страницы. В тоже время каждый блок может проверять были ли эти данные уже загружены до него, и если да, то уже не вытягивать их с базы данных или еще откуда-то.

Хотел бы я сказать что сами переходы по ссылкам на сайте делаются стандартно через $.ajax(…), да вот не так, об этом чуть ниже.

Блочная структура сайта влечет за собой также несколько проблем:

  1. Проблемы с тайтлами страниц. Если раньше достаточно было в начале скрипта получить все нужные для тайтлов данные, а дальше, просто отобразить всю страницу на основе этих же данных, то теперь мы в начале скрипта незнаем какой контент на текущей странице должен отобразится, так как все страницы меняются через pushState из history.js. Поэтому для тайтлов нужно сделать отдельную обработку REQUEST_URI, что влечет за собой некие дополнительные телодвижения для того, чтобы потом эти данные передавать дальше по дереву блоков. Хотя наши блоки и самостоятельные и не нуждаются в этих данных, но дабы избавится от повторяющихся запросов к базе данных и т.д., то все же эти данные лучше передавать от места генерации тайтла до идентичного (похожего) запроса в блоке. Да, есть вариант с кешированием вывода конечно, но в наш шаблонизатор он никак не врезается.
  2. Проблема разных запросов. Если раньше можно было взять, допустим, с базы сразу название сериала + список эпизодов этого сериала одним запросов, положить в один массив и отобразить его, то теперь мы должны получать отдельным запросом название сериала и отдельным запросом список эпизодов. Так как это разные блоки и один блок находится в самом верху страницы, а второй где-то после 10 прокруток вниз и любой из этих блоков может быть изменен с помощью javascript’a

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

Javascript

А теперь самое интересное. Каждый браузер имеет такую кнопку как переход назад. И если у вас не ajax сайт, то никаких проблем с ней нету. Даже если у вас всего пару ajax подгрузок на сайте, то тоже нету с этим никаких проблем. А вот если ваш сайт полностью работает без перезагрузок, тогда на эту кнопку приходится тратить немало сил. Мало того, если сначала написать весь сайт без перезагрузок страниц, а потом в конце решить добавить handle на эту кнопку, то хлопот будет выше крыши.

Большинство разработчиков предпочитают просто отловить событие перехода назад (еще фокус в том, что переход назад ничем не отличается от перехода вперед да и вообще от обычной ajax загрузки) и перезагрузить страниц полностью. Но для нас фанатов идеальных реализаций это явно неправильная дорога. Поэтому решено было написать механизм на основе localStorage с запоминанием страницы полностью. Прелесть подхода в том, что страница грузится моментально, нету никаких подгрузок, но с этим одним плюсом тянется куча других сложностей и проблем:

  1. Размер localStorage
    На каждом устройстве localStorage имеет заранее определенный максимальный размер. Поэтому если ваша страница в него не влезает, то она будет просто откинута и возможность у пользователя перейти назад или вперед просто не будет. Поэтому приходится заранее ограничивать глубину похода пользователя назад или вперед, а также создавать пул страниц. В среднем оно ограничено 5МБайтами, но на мобильных устройствах более-менее безопасно брать 2МБ. И это все при условии, что пользователь сам не tweak’ал его.
  2. Необходимость в различных состояниях страницы перед сохранением
    К примеру, вы зашли на страницу где по вашему хотению вылетел popup, в нём вы кликнули еще на одну ссылку и перешли на следующую. И тут вы решили перейти обратно. Конечно же при переходе обратно этот popup также там остался, потому что на момент перехода мы сохранили полную копию страницы с видимым popup’ом. Для решения этой проблемы приходится создавать callback pull запрос, который выполняет все необходимые эвенты при уходе со страницы. При этом состояние javascript переменных нужно сохранять в DOM дереве, иначе при возращении на эту страницу ничего работать так как нужно не будет, потому что при доставании с localStorage мы не достаем значение переменных javascript
  3. Отключить автоудаление javascript'a
    А вот здесь уже действительно нас ожидала засада. Дело в том, что когда мы сохранили исходный код страницы в локальное хранилище, то при вытягивании его оттуда нужно также чтобы и javascript’ы были рабочими. Так как у нас блочная структура, то скрипты могут находится в начале страницы, в середине, да где угодно. Кроме этого при загрузке блока нужно еще выполнить всякие $(document).ready(…), $().ready() (ой, это кажется одно и тоже ;))и т.д. Итак, приступим к реализации.

    Тянем контент через $.ajax(…), вставляет на страницу с помощью $(‘#block’).html(‘контент’); и при уходе со страницы(или входе) сохраняем весь исходный код страницы в локальное хранилище. Дальше при переходе назад ищем в нашем локальном пуле есть ли эта страница, и если есть, то вставляем через тот же .html(‘страница’). Жмем по ссылкам и … ничего не пашет. Javascript не выполнился при загрузке страницы, да и вообще его нету на странице. Где делся?
    А вот собака оказывается зарыта в методе .html() из JQuery. При вставке контента он находит javascript, выполняет его, и удаляет из html кода. Таким образом на странице мы имеем чистый html и в локальный пул при уходе также сохраняем чистый html. Поэтому, если нужно вставить HTML код который имеет javascript то вставляем его только через innerHTML = ‘код’;
    Сказано сделано, вставили, загрузили, опять смотрим и … ничего не пашет. А теперь мы видим, что javascript есть на странице, но ничего из него не выполняется, он присутствует как обычный HTML. Значит делаем вторые костыли.
    Сначала нужно найти все <script src=''></script> теги и повыдёргивать адреса которые должны подгрузиться. Подгружаем их контент в массив, при этом распарсиваем оставшийся javascript код который есть на странице(в блоке).
    Дальше нужно сообщить нашему браузеру что у нас есть код который нужно понимать как javascript а не html. Для этого делаем манипуляцию вроде этой

    if (window.execScript) {
        window.execScript(scripts);
    } else {
    var head = document.getElementsByTagName('head')[0] || document.documentElement;
        var scriptElement = document.createElement('script');
        scriptElement.type = "text/javascript";
        try {
             // doesn't work on ie...
            scriptElement.appendChild(document.createTextNode(scripts));
        } catch(e) {
            // IE has funky script nodes
            scriptElement.text = scripts;
        }
        head.appendChild(scriptElement);
        head.removeChild(scriptElement);
    }
    

    Теперь весь javascript, что у нас был на странице там же и остался, кроме того, браузер его понимает как нужно.

    Ах да, чуть не забыл сказать, комментарии можно обрамлять только как /**/, // нельзя так как при кормлении такого кода браузеру через создание элемента, он считает его инвалидом, точнее невалидным ;).

  4. Повторное выполнение javascript'a при загрузке страниц
    Осталось выполнить $(document).ready(..) и вроде все.
    $(document).ready(..) JQuery выполняет 1 раз. То-есть, если вы загрузили блок где был код $(document).ready(mysuperfunction(){});, потом перешли куда-то, а потом опять загрузили этот же блок, то второй раз уже функция “mysuperfunction” не выполнится.
    Дабы не разбираться в дебрях JQuery и не вытаскивать из него местоположение closure функции переданной .ready() + не зависеть от его внутрянки, каждой автозагрузке дали особое начало имени $(preDOM_*);. Тем самым, когда распарсиваем весь javascript(а мы ведь все равно это делаем), то просто собираем эти функции и потом их выполняем. Конечно при такой реализации нужно следить чтобы функция “mysuperfunction()” не вызвалась дважды (сначала jquery вызвал а потом еще мы догнали и добавили). Поэтому приходится в начале каждой функции добавлять маркер.
    Итого код

    $(document).ready(function(){
        //test
        Console.log(‘test’);
    });
    

    теперь выглядит теперь так:

    function preDOM_mysuperfunction (){
        $().markFunction(‘preDOM_mysuperfunction’);
        /*test новый вид комментария*/
        Console.log(‘test’);
    }
    $(preDOM_mysuperfunction);
    

С Javascript’ом вроде закончили.

Подытожим

  1. Каждый блок который будет меняться на сайте должен быть самодостаточен и не зависеть ни от каких переменных выше. Максимально что он может это использовать уже полученные данные ранее и то если их нету он должен уметь их сам доставать
  2. Решить проблему повторной загрузки блока (так как .ready() срабатывает только единожды) при помощи своей структуры функций или же выковыривать closure из jquery и потом их вызывать. В любом из этих случаев все равно придется их самостоятельно вызвать
  3. Отключить кнопку назад (просто полностью перезагружать страницу) или попробовать способ, описанный выше с LocalStorage и распарсиванием всего javascript кода

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

Если кому-то интересно будет посмотреть, как же оно в реальности работает то можете перейти по этой ссылке. Там конечно еще закрытая бета, но пощупать можно. И мой профайл (доступ только после ссылки выше) конечно же чтобы было понятно насколько наболело ;)

P.S. Заранее простите что попытался втыкнуть какой-то юмор и вставил 2 смайлика в столь строгую статью.

Автор: jimmi

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js